Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,35 @@ folderA/
```
</details>

#### Skipping subtrees without markdown

Passing `--skip-subtrees-wo-markdown` will skip any directory subtree that does not contain any markdown files. This is useful when your folder structure contains non-documentation directories (e.g. images, data, configurations) that you don't want to appear in Confluence.

<details>
<summary>Example</summary>
```text
document.md
docs/
guide.md
images/
icons/
favicon.ico
logo.png
data/
config.yaml
```

will be uploaded as:

```text
document
docs/
guide
```

The `images/` and `data/` subtrees are entirely skipped because they contain no markdown files.
</details>

## Terminal output format

By default, `md2cf` produces rich output with animated progress bars that are meant for human consumption. If the output is redirected to a file, the progress bars will not be displayed and only the final result will be written to the file. Error messages are always printed to standard error.
Expand Down
6 changes: 6 additions & 0 deletions md2cf/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ def get_parser():
action="store_true",
help="if a folder doesn't contain documents, skip it",
)
dir_group.add_argument(
"--skip-subtrees-wo-markdown",
action="store_true",
help="skip directory subtrees that do not contain any markdown files",
)

relative_links_group = parser.add_argument_group("relative links arguments")
relative_links_group.add_argument(
Expand Down Expand Up @@ -697,6 +702,7 @@ def collect_pages_to_upload(args):
use_pages_file=args.use_pages_file,
use_gitignore=args.use_gitignore,
enable_relative_links=args.enable_relative_links,
skip_subtrees_wo_markdown=args.skip_subtrees_wo_markdown,
)
else:
try:
Expand Down
22 changes: 22 additions & 0 deletions md2cf/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ def __repr__(self):
)


def _subtree_has_markdown(path: Path, git_repo: GitRepository) -> bool:
"""Check if a directory tree contains any .md files (respecting gitignore)."""
for dirpath, _, filenames in os.walk(path):
dirpath = Path(dirpath).resolve()
if git_repo.is_ignored(dirpath):
continue
for fname in filenames:
if fname.endswith(".md") and not git_repo.is_ignored(dirpath / fname):
return True
return False


def find_non_empty_parent_path(
current_dir: Path, folder_data: Dict[Path, Dict[str, Any]], default: Path
) -> Path:
Expand All @@ -91,6 +103,7 @@ def get_pages_from_directory(
remove_text_newlines: bool = False,
use_gitignore: bool = True,
enable_relative_links: bool = False,
skip_subtrees_wo_markdown: bool = False,
) -> List[Page]:
"""
Collect a list of markdown files recursively under the file_path directory.
Expand All @@ -107,6 +120,8 @@ def get_pages_from_directory(
search
:param enable_relative_links: extract all relative links and replace them with
placeholders
:param skip_subtrees_wo_markdown: skip directory subtrees that contain no markdown
files
:return: A list of paths to the markdown files to upload.
"""
processed_pages = list()
Expand All @@ -130,6 +145,13 @@ def get_pages_from_directory(
path for path in markdown_files if not git_repo.is_ignored(path)
]

if skip_subtrees_wo_markdown:
directories[:] = [
d
for d in directories
if _subtree_has_markdown(Path(current_path, d), git_repo)
]

folder_data[current_path] = {"n_files": len(markdown_files)}

# we'll capture title and path of the parent folder for this folder:
Expand Down
91 changes: 91 additions & 0 deletions test_package/unit/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,94 @@ def test_get_document_frontmatter_empty():
"""

assert doc.get_document_frontmatter(source_markdown.splitlines(keepends=True)) == {}


def test_get_pages_from_directory_skip_subtrees_wo_markdown(fs):
"""Subtrees without any markdown files are skipped entirely."""
fs.create_file("/root-folder/docs/readme.md")
fs.create_file("/root-folder/images/logo.png")
fs.create_dir("/root-folder/images/icons")
fs.create_file("/root-folder/images/icons/favicon.ico")
fs.create_file("/root-folder/data/config.yaml")

result = doc.get_pages_from_directory(
Path("/root-folder"), skip_subtrees_wo_markdown=True
)
assert result == [
FakePage(title="docs", file_path=None, parent_title=None),
FakePage(
title="readme",
file_path=Path("/root-folder/docs/readme.md"),
parent_title="docs",
),
]


def test_get_pages_from_directory_skip_subtrees_wo_markdown_nested(fs):
"""A subtree with markdown deeply nested is kept, but sibling subtrees without
markdown are pruned."""
fs.create_file("/root-folder/a/b/c/deep.md")
fs.create_file("/root-folder/a/b/other/data.txt")
fs.create_file("/root-folder/empty-tree/sub/file.txt")

result = doc.get_pages_from_directory(
Path("/root-folder"), skip_subtrees_wo_markdown=True
)
assert result == [
FakePage(title="a", file_path=None, parent_title=None),
FakePage(title="b", file_path=None, parent_title="a"),
FakePage(title="c", file_path=None, parent_title="b"),
FakePage(
title="deep",
file_path=Path("/root-folder/a/b/c/deep.md"),
parent_title="c",
),
]


def test_get_pages_from_directory_skip_subtrees_wo_markdown_root_has_md(fs):
"""Root-level markdown files are still included; only subtrees without markdown
are pruned."""
fs.create_file("/root-folder/index.md")
fs.create_file("/root-folder/no-md/data.csv")

result = doc.get_pages_from_directory(
Path("/root-folder"), skip_subtrees_wo_markdown=True
)
assert result == [
FakePage(
title="index",
file_path=Path("/root-folder/index.md"),
parent_title=None,
),
]


def test_get_pages_from_directory_skip_subtrees_wo_markdown_all_empty(fs):
"""When no subtree contains markdown, nothing is returned."""
fs.create_file("/root-folder/images/logo.png")
fs.create_file("/root-folder/data/config.yaml")

result = doc.get_pages_from_directory(
Path("/root-folder"), skip_subtrees_wo_markdown=True
)
assert result == []


def test_get_pages_from_directory_skip_subtrees_wo_markdown_disabled(fs):
"""Without the flag, subtrees without markdown are still traversed and produce
folder pages when they have subdirectories."""
fs.create_file("/root-folder/docs/readme.md")
fs.create_file("/root-folder/images/icons/favicon.ico")

result_with = doc.get_pages_from_directory(
Path("/root-folder"), skip_subtrees_wo_markdown=True
)
result_without = doc.get_pages_from_directory(
Path("/root-folder"), skip_subtrees_wo_markdown=False
)
# With the flag, images/ subtree is excluded
assert FakePage(title="images") not in result_with
# Without the flag, images/ subtree is included as a folder page
# (it has a subdirectory, so it gets a page entry)
assert FakePage(title="images") in result_without