markitecture 0.1.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- markitecture/__init__.py +41 -0
- markitecture/__main__.py +4 -0
- markitecture/cli/__init__.py +3 -0
- markitecture/cli/app.py +38 -0
- markitecture/cli/commands/__init__.py +21 -0
- markitecture/cli/commands/config.py +84 -0
- markitecture/cli/commands/links.py +146 -0
- markitecture/cli/commands/metrics.py +193 -0
- markitecture/cli/commands/mkdocs.py +39 -0
- markitecture/cli/commands/split.py +48 -0
- markitecture/errors.py +64 -0
- markitecture/generators/__init__.py +3 -0
- markitecture/generators/configs/__init__.py +0 -0
- markitecture/generators/configs/mintlify_json.py +0 -0
- markitecture/generators/configs/mkdocs_yaml.py +317 -0
- markitecture/metrics/__init__.py +9 -0
- markitecture/metrics/analyzer.py +109 -0
- markitecture/metrics/badges/__init__.py +28 -0
- markitecture/metrics/badges/base.py +7 -0
- markitecture/metrics/badges/compact.py +35 -0
- markitecture/metrics/badges/detailed.py +60 -0
- markitecture/metrics/badges/minimal.py +19 -0
- markitecture/metrics/badges/modern.py +45 -0
- markitecture/metrics/badges/retro.py +23 -0
- markitecture/metrics/badges/shields.py +124 -0
- markitecture/metrics/svg_generator.py +70 -0
- markitecture/processing/__init__.py +0 -0
- markitecture/processing/link_validator.py +133 -0
- markitecture/processing/reflink_converter.py +198 -0
- markitecture/processing/reflink_extractor.py +82 -0
- markitecture/processing/text_splitter.py +290 -0
- markitecture/settings/__init__.py +9 -0
- markitecture/settings/config.py +61 -0
- markitecture/settings/validators.py +26 -0
- markitecture/utils/__init__.py +5 -0
- markitecture/utils/file_handler.py +24 -0
- markitecture/utils/printer.py +195 -0
- markitecture/utils/sanitizer.py +78 -0
- markitecture-0.1.15.dist-info/METADATA +271 -0
- markitecture-0.1.15.dist-info/RECORD +43 -0
- markitecture-0.1.15.dist-info/WHEEL +4 -0
- markitecture-0.1.15.dist-info/entry_points.txt +2 -0
- markitecture-0.1.15.dist-info/licenses/LICENSE +21 -0
markitecture/__init__.py
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
from importlib.metadata import version
|
2
|
+
|
3
|
+
from markitecture import metrics
|
4
|
+
|
5
|
+
from .errors import (
|
6
|
+
FileOperationError,
|
7
|
+
FileReadError,
|
8
|
+
FileWriteError,
|
9
|
+
InvalidPathError,
|
10
|
+
MarkitectureBaseError,
|
11
|
+
ParseError,
|
12
|
+
)
|
13
|
+
from .generators.configs.mkdocs_yaml import MkDocsConfig
|
14
|
+
from .metrics.svg_generator import MetricsSvgGenerator
|
15
|
+
from .processing.reflink_converter import (
|
16
|
+
ReferenceLinkConverter,
|
17
|
+
ReferencePlacement,
|
18
|
+
)
|
19
|
+
from .processing.text_splitter import MarkdownTextSplitter
|
20
|
+
from .utils.file_handler import FileHandler
|
21
|
+
from .utils.printer import RichPrinter
|
22
|
+
|
23
|
+
__version__ = version("markitecture")
|
24
|
+
|
25
|
+
__all__: list[str] = [
|
26
|
+
"FileHandler",
|
27
|
+
"FileOperationError",
|
28
|
+
"FileReadError",
|
29
|
+
"FileWriteError",
|
30
|
+
"InvalidPathError",
|
31
|
+
"MarkdownTextSplitter",
|
32
|
+
"MarkitectureBaseError",
|
33
|
+
"MetricsSvgGenerator",
|
34
|
+
"MkDocsConfig",
|
35
|
+
"ParseError",
|
36
|
+
"ReferenceLinkConverter",
|
37
|
+
"ReferencePlacement",
|
38
|
+
"RichPrinter",
|
39
|
+
"__version__",
|
40
|
+
"metrics",
|
41
|
+
]
|
markitecture/__main__.py
ADDED
markitecture/cli/app.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
from markitecture.settings.config import MarkitectureApp
|
2
|
+
from markitecture.utils.printer import RichPrinter
|
3
|
+
|
4
|
+
_printer = RichPrinter()
|
5
|
+
|
6
|
+
|
7
|
+
def run_cli() -> None:
|
8
|
+
"""
|
9
|
+
Main entry point for the CLI. Routes commands to their appropriate handlers.
|
10
|
+
"""
|
11
|
+
from markitecture import __version__
|
12
|
+
|
13
|
+
try:
|
14
|
+
settings = MarkitectureApp()
|
15
|
+
if settings.version:
|
16
|
+
_printer.print_version(__version__)
|
17
|
+
return
|
18
|
+
|
19
|
+
if settings.config:
|
20
|
+
settings.config.cli_cmd()
|
21
|
+
elif settings.check_links:
|
22
|
+
settings.check_links.cli_cmd()
|
23
|
+
elif settings.reference_links:
|
24
|
+
settings.reference_links.cli_cmd()
|
25
|
+
elif settings.metrics:
|
26
|
+
settings.metrics.cli_cmd()
|
27
|
+
elif settings.mkdocs:
|
28
|
+
settings.mkdocs.cli_cmd()
|
29
|
+
elif settings.split:
|
30
|
+
settings.split.cli_cmd()
|
31
|
+
else:
|
32
|
+
_printer.print_error(
|
33
|
+
"No command provided. Use `--help` for more information."
|
34
|
+
)
|
35
|
+
|
36
|
+
except Exception as e:
|
37
|
+
_printer.print_error(f"An error occurred: {e!r}")
|
38
|
+
raise e
|
@@ -0,0 +1,21 @@
|
|
1
|
+
"""
|
2
|
+
Command implementations for the Markitecture CLI.
|
3
|
+
|
4
|
+
This module contains all available commands that can be executed through the CLI.
|
5
|
+
Each command is implemented as a separate class inheriting from BaseCommand.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .config import ConfigCommand
|
9
|
+
from .links import CheckLinksCommand, ReferenceLinksCommand
|
10
|
+
from .metrics import MetricsCommand
|
11
|
+
from .mkdocs import MkDocsCommand
|
12
|
+
from .split import SplitCommand
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"CheckLinksCommand",
|
16
|
+
"ConfigCommand",
|
17
|
+
"MetricsCommand",
|
18
|
+
"MkDocsCommand",
|
19
|
+
"ReferenceLinksCommand",
|
20
|
+
"SplitCommand",
|
21
|
+
]
|
@@ -0,0 +1,84 @@
|
|
1
|
+
"""CLI command for managing configurations via YAML files."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import yaml
|
6
|
+
from pydantic import AliasChoices, BaseModel, Field, field_validator
|
7
|
+
|
8
|
+
from markitecture.settings.validators import convert_to_path
|
9
|
+
from markitecture.utils.printer import RichPrinter
|
10
|
+
|
11
|
+
_printer = RichPrinter()
|
12
|
+
|
13
|
+
|
14
|
+
class ConfigCommand(BaseModel):
|
15
|
+
"""
|
16
|
+
CLI command for managing configurations via YAML files.
|
17
|
+
"""
|
18
|
+
|
19
|
+
config_path: Path = Field(
|
20
|
+
default=Path("markitect.yml"),
|
21
|
+
description="Path to the configuration file.",
|
22
|
+
validation_alias=AliasChoices("p", "path"),
|
23
|
+
)
|
24
|
+
generate: bool = Field(
|
25
|
+
default=False,
|
26
|
+
description="Generate a default configuration file.",
|
27
|
+
validation_alias=AliasChoices("g", "generate"),
|
28
|
+
)
|
29
|
+
show: bool = Field(
|
30
|
+
default=False,
|
31
|
+
description="Display the current configuration settings.",
|
32
|
+
validation_alias=AliasChoices("s", "show"),
|
33
|
+
)
|
34
|
+
|
35
|
+
validate_fields = field_validator("config_path")(convert_to_path)
|
36
|
+
|
37
|
+
def cli_cmd(self) -> None:
|
38
|
+
"""Execute the configuration command."""
|
39
|
+
if self.generate:
|
40
|
+
self.generate_config()
|
41
|
+
|
42
|
+
if self.show:
|
43
|
+
self.show_config()
|
44
|
+
|
45
|
+
def generate_config(self) -> None:
|
46
|
+
"""Generates a default configuration file."""
|
47
|
+
from markitecture.cli.app import MarkitectureApp
|
48
|
+
|
49
|
+
_printer.print_info(
|
50
|
+
f"Generating default configuration file at {self.config_path}"
|
51
|
+
)
|
52
|
+
settings = MarkitectureApp()
|
53
|
+
settings_dict = settings.model_dump(mode="json")
|
54
|
+
|
55
|
+
with self.config_path.open("w", encoding="utf-8") as file:
|
56
|
+
yaml.dump(
|
57
|
+
settings_dict,
|
58
|
+
file,
|
59
|
+
default_flow_style=False,
|
60
|
+
sort_keys=False,
|
61
|
+
)
|
62
|
+
_printer.print_success(
|
63
|
+
f"Markitecture configuration file generated at: {self.config_path}"
|
64
|
+
)
|
65
|
+
|
66
|
+
def show_config(self) -> None:
|
67
|
+
"""Displays the current configuration settings."""
|
68
|
+
if self.config_path.exists():
|
69
|
+
_printer.print_debug(f"Reading configuration file: {self.config_path}")
|
70
|
+
|
71
|
+
try:
|
72
|
+
with self.config_path.open(encoding="utf-8") as file:
|
73
|
+
settings = yaml.safe_load(file)
|
74
|
+
except yaml.YAMLError as e:
|
75
|
+
_printer.print_error(f"Error reading configuration file: {e}")
|
76
|
+
return
|
77
|
+
|
78
|
+
_printer.print_key_value_table("Configuration Settings", settings)
|
79
|
+
|
80
|
+
else:
|
81
|
+
_printer.print_error(
|
82
|
+
f"No configuration file found at {self.config_path}. "
|
83
|
+
"Use '--generate' to create one."
|
84
|
+
)
|
@@ -0,0 +1,146 @@
|
|
1
|
+
"""Commands for checking and converting markdown links."""
|
2
|
+
|
3
|
+
import re
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from pydantic import AliasChoices, BaseModel, Field
|
7
|
+
|
8
|
+
from markitecture.processing.link_validator import LinkValidator
|
9
|
+
from markitecture.processing.reflink_converter import (
|
10
|
+
ReferenceLinkConverter,
|
11
|
+
ReferencePlacement,
|
12
|
+
)
|
13
|
+
from markitecture.settings.validators import ExistingFilePath
|
14
|
+
from markitecture.utils.printer import RichPrinter
|
15
|
+
|
16
|
+
_printer = RichPrinter()
|
17
|
+
|
18
|
+
|
19
|
+
class CheckLinksCommand(BaseModel):
|
20
|
+
"""
|
21
|
+
Validate all links in a markdown file.
|
22
|
+
"""
|
23
|
+
|
24
|
+
input_file: ExistingFilePath = Field(
|
25
|
+
...,
|
26
|
+
description="Path to the markdown file.",
|
27
|
+
validation_alias=AliasChoices("i", "input"),
|
28
|
+
)
|
29
|
+
report_path: Path = Field(
|
30
|
+
default=Path(".markitecture/link_health.txt"),
|
31
|
+
description="Path to save the report.",
|
32
|
+
validation_alias=AliasChoices("rp", "report-path"),
|
33
|
+
)
|
34
|
+
max_workers: int = Field(
|
35
|
+
default=5,
|
36
|
+
ge=1,
|
37
|
+
le=10,
|
38
|
+
description="Number of concurrent link checks.",
|
39
|
+
validation_alias=AliasChoices("mw", "max-workers"),
|
40
|
+
)
|
41
|
+
timeout: int = Field(
|
42
|
+
default=10,
|
43
|
+
ge=1,
|
44
|
+
le=180,
|
45
|
+
description="Timeout for link validation in seconds.",
|
46
|
+
validation_alias=AliasChoices("t", "timeout"),
|
47
|
+
)
|
48
|
+
|
49
|
+
def cli_cmd(self) -> None:
|
50
|
+
"""Execute the check links command."""
|
51
|
+
_printer.print_info(
|
52
|
+
f"Scanning markdown file {self.input_file} for broken links..."
|
53
|
+
)
|
54
|
+
|
55
|
+
checker = LinkValidator(timeout=self.timeout, max_workers=self.max_workers)
|
56
|
+
results = checker.check_markdown_file(str(self.input_file))
|
57
|
+
if not results:
|
58
|
+
_printer.print_info("No links found.")
|
59
|
+
return
|
60
|
+
|
61
|
+
broken_links = 0
|
62
|
+
rows = []
|
63
|
+
for result in results:
|
64
|
+
status = "✓" if result["status"] == "ok" else "𝗫"
|
65
|
+
error = result["error"] if result["error"] else ""
|
66
|
+
rows.append([status, str(result["line"]), result["url"], error])
|
67
|
+
if result["error"]:
|
68
|
+
broken_links += 1
|
69
|
+
|
70
|
+
_printer.print_table(
|
71
|
+
"Markdown Link Check Results",
|
72
|
+
["Status", "Line", "Link", "Error"],
|
73
|
+
rows,
|
74
|
+
)
|
75
|
+
_printer.print_success(
|
76
|
+
f"Summary: {broken_links} broken links out of {len(results)} total links.\n"
|
77
|
+
)
|
78
|
+
|
79
|
+
|
80
|
+
class ReferenceLinksCommand(BaseModel):
|
81
|
+
"""Convert inline markdown links to reference-style links."""
|
82
|
+
|
83
|
+
input_file: ExistingFilePath = Field(
|
84
|
+
...,
|
85
|
+
description="Path to the markdown file.",
|
86
|
+
validation_alias=AliasChoices("i", "input"),
|
87
|
+
)
|
88
|
+
output_file: Path | str = Field(
|
89
|
+
default=Path("reflinks_output.md"),
|
90
|
+
description="Path to save updated document.",
|
91
|
+
validation_alias=AliasChoices("o", "output"),
|
92
|
+
)
|
93
|
+
placement: ReferencePlacement = Field(
|
94
|
+
default=ReferencePlacement.END,
|
95
|
+
description="Where to place reference links (end/section).",
|
96
|
+
validation_alias=AliasChoices("p", "placement"),
|
97
|
+
)
|
98
|
+
|
99
|
+
def cli_cmd(self) -> None:
|
100
|
+
"""Execute the reference link conversion."""
|
101
|
+
_printer.print_title("Reference Link Conversion")
|
102
|
+
_printer.print_info("Configuration:")
|
103
|
+
_printer.print_key_value_table(
|
104
|
+
"Settings",
|
105
|
+
{
|
106
|
+
"Input File": str(self.input_file),
|
107
|
+
"Output File": str(self.output_file),
|
108
|
+
"Placement": self.placement.value,
|
109
|
+
},
|
110
|
+
)
|
111
|
+
|
112
|
+
try:
|
113
|
+
# Initialize converter
|
114
|
+
converter = ReferenceLinkConverter()
|
115
|
+
content = Path(self.input_file).read_text()
|
116
|
+
|
117
|
+
# Extract initial metrics
|
118
|
+
initial_links = len(re.findall(r"\[([^\]]+)\]\(([^\)]+)\)", content))
|
119
|
+
_printer.print_info(f"Found {initial_links} inline links to convert")
|
120
|
+
|
121
|
+
# Process file
|
122
|
+
_printer.print_debug("Starting conversion process...")
|
123
|
+
converter.process_file(
|
124
|
+
self.input_file,
|
125
|
+
self.output_file or self.input_file,
|
126
|
+
self.placement,
|
127
|
+
)
|
128
|
+
|
129
|
+
# Get final metrics
|
130
|
+
result = Path(self.output_file).read_text()
|
131
|
+
final_refs = len(re.findall(r"^\[[^\]]+\]:", result, re.MULTILINE))
|
132
|
+
|
133
|
+
# Summary
|
134
|
+
_printer.print_success("\nConversion Summary:")
|
135
|
+
_printer.print_key_value_table(
|
136
|
+
"Results",
|
137
|
+
{
|
138
|
+
"Links Processed": str(initial_links),
|
139
|
+
"References Created": str(final_refs),
|
140
|
+
"Output Location": str(self.output_file),
|
141
|
+
},
|
142
|
+
)
|
143
|
+
|
144
|
+
except Exception as e:
|
145
|
+
_printer.print_error(f"Error during conversion: {e!s}")
|
146
|
+
raise
|
@@ -0,0 +1,193 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from pydantic import AliasChoices, BaseModel, Field
|
4
|
+
|
5
|
+
from markitecture.metrics.analyzer import ReadabilityAnalyzer, ReadabilityMetrics
|
6
|
+
from markitecture.metrics.svg_generator import (
|
7
|
+
BadgeStyle,
|
8
|
+
MetricsSvgGenerator,
|
9
|
+
)
|
10
|
+
from markitecture.settings.validators import ExistingFilePath
|
11
|
+
from markitecture.utils.file_handler import FileHandler
|
12
|
+
from markitecture.utils.printer import RichPrinter
|
13
|
+
|
14
|
+
_printer = RichPrinter()
|
15
|
+
|
16
|
+
|
17
|
+
class MetricsCommand(BaseModel):
|
18
|
+
"""
|
19
|
+
Generate reading time estimates and complexity metrics for markdown files.
|
20
|
+
"""
|
21
|
+
|
22
|
+
input: ExistingFilePath = Field(
|
23
|
+
...,
|
24
|
+
description="Path to the markdown file.",
|
25
|
+
validation_alias=AliasChoices("i", "input"),
|
26
|
+
)
|
27
|
+
output: Path | None = Field(
|
28
|
+
default=None,
|
29
|
+
description=f"Path to save the SVG badge. If not specified, creates {input}_metrics.svg",
|
30
|
+
validation_alias=AliasChoices("o", "output"),
|
31
|
+
)
|
32
|
+
output_dir: Path | None = Field(
|
33
|
+
default=None,
|
34
|
+
description="Directory to save all badge styles when using --style all",
|
35
|
+
validation_alias=AliasChoices("d", "dir", "output-dir"),
|
36
|
+
)
|
37
|
+
insert: bool = Field(
|
38
|
+
default=True,
|
39
|
+
description="Insert metrics badge into the document.",
|
40
|
+
validation_alias=AliasChoices("ins", "insert"),
|
41
|
+
)
|
42
|
+
position: str = Field(
|
43
|
+
default="top",
|
44
|
+
description="Position to insert badge (top/bottom).",
|
45
|
+
validation_alias=AliasChoices("p", "pos", "position"),
|
46
|
+
)
|
47
|
+
style: BadgeStyle | str = Field(
|
48
|
+
default=BadgeStyle.MODERN,
|
49
|
+
description="Badge style (modern/compact/detailed/minimal/retro) or 'all' for all styles",
|
50
|
+
validation_alias=AliasChoices("s", "style"),
|
51
|
+
)
|
52
|
+
|
53
|
+
def _generate_single_badge(
|
54
|
+
self,
|
55
|
+
metrics: ReadabilityMetrics,
|
56
|
+
style: BadgeStyle,
|
57
|
+
content: str,
|
58
|
+
output_path: Path,
|
59
|
+
) -> None:
|
60
|
+
"""Generate a single badge style."""
|
61
|
+
generator = MetricsSvgGenerator()
|
62
|
+
svg_content = generator.generate_svg(metrics, style)
|
63
|
+
|
64
|
+
# Save badge
|
65
|
+
Path(output_path).write_text(svg_content, encoding="utf-8")
|
66
|
+
_printer.print_info(f"Generated {style.value} style badge: {output_path}")
|
67
|
+
|
68
|
+
# Insert if requested and this is the primary style
|
69
|
+
if self.insert and style == self.style:
|
70
|
+
doc_content = content
|
71
|
+
svg_ref = f""
|
72
|
+
if self.position.lower() == "top":
|
73
|
+
doc_content = f"{svg_ref}\n\n{content}"
|
74
|
+
else:
|
75
|
+
doc_content = f"{content}\n\n{svg_ref}"
|
76
|
+
Path(self.input).write_text(doc_content, encoding="utf-8")
|
77
|
+
_printer.print_info(f"Metrics badge added to document at: {self.position}")
|
78
|
+
|
79
|
+
def _generate_all_badges(self, metrics: ReadabilityAnalyzer, content: str) -> None:
|
80
|
+
"""Generate badges in all available styles."""
|
81
|
+
# Create output directory if needed
|
82
|
+
output_dir = self.output_dir or Path(self.input).parent / "metrics_badges"
|
83
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
84
|
+
|
85
|
+
# Generate each style
|
86
|
+
for style in BadgeStyle:
|
87
|
+
output_path = output_dir / f"metrics_{style.value}.svg"
|
88
|
+
self._generate_single_badge(metrics, style, content, output_path)
|
89
|
+
|
90
|
+
_printer.print_info(f"\nAll badge styles generated in: {output_dir}")
|
91
|
+
|
92
|
+
# Generate preview HTML
|
93
|
+
preview_path = output_dir / "preview.html"
|
94
|
+
self._generate_preview_page(metrics, output_dir, preview_path)
|
95
|
+
_printer.print_info(f"Preview page generated: {preview_path}")
|
96
|
+
|
97
|
+
def _generate_preview_page(
|
98
|
+
self, metrics: ReadabilityMetrics, badge_dir: Path, output_path: Path
|
99
|
+
) -> None:
|
100
|
+
"""Generate HTML preview page showing all badge styles."""
|
101
|
+
html = f"""<!DOCTYPE html>
|
102
|
+
<html>
|
103
|
+
<head>
|
104
|
+
<title>Metrics Badge Styles - {self.input.name}</title>
|
105
|
+
<style>
|
106
|
+
body {{
|
107
|
+
font-family: Arial, sans-serif;
|
108
|
+
max-width: 800px;
|
109
|
+
margin: 40px auto;
|
110
|
+
padding: 0 20px;
|
111
|
+
background: #f5f5f5;
|
112
|
+
}}
|
113
|
+
.badge-container {{
|
114
|
+
background: white;
|
115
|
+
border-radius: 8px;
|
116
|
+
padding: 20px;
|
117
|
+
margin: 20px 0;
|
118
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
119
|
+
}}
|
120
|
+
h1 {{
|
121
|
+
color: #333;
|
122
|
+
border-bottom: 2px solid #eee;
|
123
|
+
padding-bottom: 10px;
|
124
|
+
}}
|
125
|
+
h2 {{
|
126
|
+
color: #666;
|
127
|
+
margin-top: 30px;
|
128
|
+
}}
|
129
|
+
.badge {{
|
130
|
+
margin: 20px 0;
|
131
|
+
}}
|
132
|
+
.style-name {{
|
133
|
+
color: #888;
|
134
|
+
font-size: 0.9em;
|
135
|
+
margin-bottom: 10px;
|
136
|
+
}}
|
137
|
+
</style>
|
138
|
+
</head>
|
139
|
+
<body>
|
140
|
+
<h1>Metrics Badge Styles</h1>
|
141
|
+
<p>Document: <strong>{self.input.name}</strong></p>
|
142
|
+
"""
|
143
|
+
|
144
|
+
# Add each badge
|
145
|
+
for style in BadgeStyle:
|
146
|
+
badge_path = f"metrics_{style.value}.svg"
|
147
|
+
html += f'''
|
148
|
+
<div class="badge-container">
|
149
|
+
<div class="style-name">{style.value.title()} Style</div>
|
150
|
+
<div class="badge">
|
151
|
+
<img src="{badge_path}" alt="Metrics Badge - {style.value} Style">
|
152
|
+
</div>
|
153
|
+
</div>'''
|
154
|
+
|
155
|
+
html += """
|
156
|
+
</body>
|
157
|
+
</html>"""
|
158
|
+
|
159
|
+
output_path.write_text(html, encoding="utf-8")
|
160
|
+
|
161
|
+
def cli_cmd(self) -> None:
|
162
|
+
"""Execute the metrics generation command."""
|
163
|
+
_printer.print_info(f"Analyzing document metrics for: {self.input}")
|
164
|
+
|
165
|
+
# Read content and generate metrics
|
166
|
+
content = FileHandler().read(self.input)
|
167
|
+
analyzer = ReadabilityAnalyzer()
|
168
|
+
metrics = analyzer.analyze_document(content)
|
169
|
+
|
170
|
+
# Generate badges based on style option
|
171
|
+
if self.style == "all":
|
172
|
+
self._generate_all_badges(metrics, content)
|
173
|
+
else:
|
174
|
+
output_path = self.output or Path(f"{self.input.stem}_metrics.svg")
|
175
|
+
self._generate_single_badge(
|
176
|
+
metrics, BadgeStyle(self.style), content, output_path
|
177
|
+
)
|
178
|
+
|
179
|
+
# Display metrics summary
|
180
|
+
_printer.print_title("Document Metrics Summary")
|
181
|
+
_printer.print_key_value_table(
|
182
|
+
"Metrics",
|
183
|
+
{
|
184
|
+
"Reading Time": f"{metrics.reading_time_mins} minutes",
|
185
|
+
"Word Count": f"{metrics.word_count:,}",
|
186
|
+
"Complexity Score": f"{metrics.complexity_score}%",
|
187
|
+
"Average Words/Sentence": f"{metrics.avg_words_per_sentence:.1f}",
|
188
|
+
"Headings": f"{metrics.heading_count}",
|
189
|
+
"Code Blocks": f"{metrics.code_block_count}",
|
190
|
+
"Links": f"{metrics.link_count}",
|
191
|
+
"Images": f"{metrics.image_count}",
|
192
|
+
},
|
193
|
+
)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from pydantic import AliasChoices, BaseModel, Field, field_validator
|
4
|
+
|
5
|
+
from markitecture.generators.configs.mkdocs_yaml import MkDocsConfig
|
6
|
+
from markitecture.settings.validators import convert_to_path
|
7
|
+
from markitecture.utils.printer import RichPrinter
|
8
|
+
|
9
|
+
_printer = RichPrinter()
|
10
|
+
|
11
|
+
|
12
|
+
class MkDocsCommand(BaseModel):
|
13
|
+
"""
|
14
|
+
Generate a basic MkDocs configuration.
|
15
|
+
"""
|
16
|
+
|
17
|
+
docs_dir: Path = Field(
|
18
|
+
default=Path(".markitecture/docs"),
|
19
|
+
description="Path to the documentation directory.",
|
20
|
+
validation_alias=AliasChoices("d", "dir", "docs-dir"),
|
21
|
+
)
|
22
|
+
site_name: str = Field(
|
23
|
+
default="MkDocs Static Site Documentation",
|
24
|
+
description="Name of the MkDocs site.",
|
25
|
+
validation_alias=AliasChoices("name", "site-name"),
|
26
|
+
)
|
27
|
+
|
28
|
+
validate_fields = field_validator("docs_dir")(convert_to_path)
|
29
|
+
|
30
|
+
def cli_cmd(self) -> None:
|
31
|
+
"""Execute MkDocs configuration generation."""
|
32
|
+
_printer.print_info(
|
33
|
+
f"Generating MkDocs static site config for: {self.docs_dir}"
|
34
|
+
)
|
35
|
+
MkDocsConfig(
|
36
|
+
docs_dir=self.docs_dir,
|
37
|
+
site_name=self.site_name,
|
38
|
+
).generate_config()
|
39
|
+
_printer.print_info(f"MkDocs config generated and saved to: {self.docs_dir}.")
|
@@ -0,0 +1,48 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from pydantic import AliasChoices, BaseModel, Field
|
4
|
+
|
5
|
+
from markitecture.settings.validators import ExistingFilePath
|
6
|
+
from markitecture.utils.file_handler import FileHandler
|
7
|
+
from markitecture.utils.printer import RichPrinter
|
8
|
+
|
9
|
+
_printer = RichPrinter()
|
10
|
+
|
11
|
+
|
12
|
+
class SplitCommand(BaseModel):
|
13
|
+
"""
|
14
|
+
Split a markdown file into sections based on headings.
|
15
|
+
"""
|
16
|
+
|
17
|
+
input_file: ExistingFilePath = Field(
|
18
|
+
...,
|
19
|
+
description="Path to the input markdown file.",
|
20
|
+
validation_alias=AliasChoices("i", "input"),
|
21
|
+
)
|
22
|
+
output_dir: Path = Field(
|
23
|
+
default=Path(".markitecture/docs"),
|
24
|
+
description="Directory to save split files.",
|
25
|
+
validation_alias=AliasChoices("o", "output"),
|
26
|
+
)
|
27
|
+
heading_level: str = Field(
|
28
|
+
default="##",
|
29
|
+
description="Heading level to split on (e.g., '#', '##').",
|
30
|
+
validation_alias=AliasChoices("hl", "heading", "level", "heading-level"),
|
31
|
+
)
|
32
|
+
case_sensitive: bool = Field(
|
33
|
+
default=False,
|
34
|
+
description="Enable case-sensitive heading matching.",
|
35
|
+
validation_alias=AliasChoices("cs", "case-sensitive"),
|
36
|
+
)
|
37
|
+
|
38
|
+
def cli_cmd(self) -> None:
|
39
|
+
"""Execute the split command."""
|
40
|
+
from markitecture.processing.text_splitter import MarkdownTextSplitter
|
41
|
+
|
42
|
+
_printer.print_info(f"Splitting Markdown file: {self.input_file}")
|
43
|
+
_printer.print_info(f"Splitting on heading level: {self.heading_level}")
|
44
|
+
splitter = MarkdownTextSplitter()
|
45
|
+
content = FileHandler().read(self.input_file)
|
46
|
+
# splitter.settings = self.model_dump()
|
47
|
+
splitter.process_file(content)
|
48
|
+
_printer.print_info(f"Split completed. Files saved to: {self.output_dir}")
|
markitecture/errors.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
"""Custom exceptions for the markitecture package."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
# ----- Base ----- #
|
8
|
+
|
9
|
+
|
10
|
+
class MarkitectureBaseError(Exception):
|
11
|
+
"""Base exception for markitecture errors."""
|
12
|
+
|
13
|
+
...
|
14
|
+
|
15
|
+
|
16
|
+
class ParseError(MarkitectureBaseError):
|
17
|
+
"""Raised when parsing markdown content fails."""
|
18
|
+
|
19
|
+
...
|
20
|
+
|
21
|
+
|
22
|
+
class FileOperationError(MarkitectureBaseError):
|
23
|
+
"""Raised when file operations fail."""
|
24
|
+
|
25
|
+
...
|
26
|
+
|
27
|
+
|
28
|
+
# ----- CLI ----- #
|
29
|
+
|
30
|
+
|
31
|
+
class CLIError(MarkitectureBaseError):
|
32
|
+
"""Exceptions related to the CLI."""
|
33
|
+
|
34
|
+
def __init__(self, message: str, *args: Any) -> None:
|
35
|
+
super().__init__(f"Invalid option provided to CLI: {message}", *args)
|
36
|
+
|
37
|
+
|
38
|
+
# ----- File IO ----- #
|
39
|
+
|
40
|
+
|
41
|
+
class FileSystemError(MarkitectureBaseError):
|
42
|
+
"""Exceptions related to file system operations."""
|
43
|
+
|
44
|
+
def __init__(self, message: str, path: str, *args: Any) -> None:
|
45
|
+
self.file_path = path
|
46
|
+
super().__init__(f"{message}: {path}", *args)
|
47
|
+
|
48
|
+
|
49
|
+
class FileReadError(FileSystemError):
|
50
|
+
"""Could not read file."""
|
51
|
+
|
52
|
+
...
|
53
|
+
|
54
|
+
|
55
|
+
class FileWriteError(FileSystemError):
|
56
|
+
"""Could not write file."""
|
57
|
+
|
58
|
+
...
|
59
|
+
|
60
|
+
|
61
|
+
class InvalidPathError(FileSystemError):
|
62
|
+
"""Invalid path provided."""
|
63
|
+
|
64
|
+
...
|