-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
161 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
"cSpell.words": [ | ||
"blockquote", | ||
"levelname", | ||
"mdignore", | ||
"mmdc", | ||
"scandir", | ||
"webui" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import os.path | ||
from dataclasses import dataclass | ||
from fnmatch import fnmatch | ||
from pathlib import Path | ||
from typing import Iterable, List, Optional | ||
|
||
|
||
@dataclass | ||
class MatcherOptions: | ||
""" | ||
Options for checking against a list of exclude/include patterns. | ||
:param source: File name to read exclusion rules from. | ||
:param extension: Extension to narrow down search to. | ||
""" | ||
|
||
source: str | ||
extension: Optional[str] = None | ||
|
||
def __post_init__(self) -> None: | ||
if self.extension is not None and not self.extension.startswith("."): | ||
self.extension = f".{self.extension}" | ||
|
||
|
||
class Matcher: | ||
"Compares file and directory names against a list of exclude/include patterns." | ||
|
||
options: MatcherOptions | ||
rules: List[str] | ||
|
||
def __init__(self, options: MatcherOptions, directory: Path) -> None: | ||
self.options = options | ||
if os.path.exists(directory / options.source): | ||
with open(directory / options.source, "r") as f: | ||
rules = f.read().splitlines() | ||
self.rules = [rule for rule in rules if rule and not rule.startswith("#")] | ||
else: | ||
self.rules = [] | ||
|
||
def extension_matches(self, name: str) -> bool: | ||
"True if the file name has the expected extension." | ||
|
||
return self.options.extension is None or name.endswith(self.options.extension) | ||
|
||
def is_excluded(self, name: str) -> bool: | ||
"True if the file or directory name matches any of the exclusion patterns." | ||
|
||
if name.startswith("."): | ||
return True | ||
|
||
if not self.extension_matches(name): | ||
return True | ||
|
||
for rule in self.rules: | ||
if fnmatch(name, rule): | ||
return True | ||
else: | ||
return False | ||
|
||
def is_included(self, name: str) -> bool: | ||
"True if the file or directory name matches none of the exclusion patterns." | ||
|
||
return not self.is_excluded(name) | ||
|
||
def filter(self, items: Iterable[str]) -> List[str]: | ||
""" | ||
Returns only those elements from the input that don't match any of the exclusion rules. | ||
:param items: A list of names to filter. | ||
:returns: A filtered list of names that didn't match any of the exclusion rules. | ||
""" | ||
|
||
return [item for item in items if self.is_included(item)] | ||
|
||
def scandir(self, path: Path) -> List[str]: | ||
""" | ||
Returns only those entries in a directory whose name doesn't match any of the exclusion rules. | ||
:param path: Directory to scan. | ||
:returns: A filtered list of entries whose name didn't match any of the exclusion rules. | ||
""" | ||
|
||
return self.filter(entry.name for entry in os.scandir(path)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# skip comments | ||
|
||
[i][g][n][!abcdefghijklmn]?[e].md* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## Ignored files | ||
|
||
This Markdown document is skipped as per the rules defined in `.mdignore`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import logging | ||
import os | ||
import os.path | ||
import unittest | ||
from pathlib import Path | ||
|
||
from md2conf.matcher import Matcher, MatcherOptions | ||
|
||
logging.basicConfig( | ||
level=logging.INFO, | ||
format="%(asctime)s - %(levelname)s - %(funcName)s [%(lineno)d] - %(message)s", | ||
) | ||
|
||
|
||
class TestMatcher(unittest.TestCase): | ||
def test_extension(self) -> None: | ||
directory = Path(os.path.dirname(__file__)) | ||
expected = [ | ||
entry.name for entry in os.scandir(directory) if entry.name.endswith(".py") | ||
] | ||
|
||
options = MatcherOptions(".mdignore", ".py") | ||
matcher = Matcher(options, directory) | ||
actual = matcher.scandir(directory) | ||
|
||
self.assertCountEqual(expected, actual) | ||
|
||
def test_rules(self) -> None: | ||
directory = Path(os.path.dirname(__file__)) / "source" | ||
expected = [ | ||
entry.name for entry in os.scandir(directory) if entry.name.endswith(".md") | ||
] | ||
expected.remove("ignore.md") | ||
|
||
options = MatcherOptions(".mdignore", ".md") | ||
matcher = Matcher(options, directory) | ||
actual = matcher.scandir(directory) | ||
|
||
self.assertCountEqual(expected, actual) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |