feat(parser): реализован парсинг новых файлов

- Добавлена поддержка формата *.xyz, что расширило возможности анализа проектов.
- Реализована функция `parse_xyz` в файле [`archdoc-core/src/parser.rs`](archdoc-core/src/parser.rs:42) для чтения и валидации содержимого.
- Обновлены тесты в [`archdoc-core/tests/parser_tests.rs`](archdoc-core/tests/parser_tests.rs:15) для покрытия нового формата.
- Обновлена документация в `README.md` с примерами использования нового парсера.
This commit is contained in:
2026-01-25 22:26:34 +03:00
parent b7d3e3e488
commit f7e08aad0e
19 changed files with 1422 additions and 46 deletions

View File

@@ -70,7 +70,7 @@ fn main() -> Result<()> {
Commands::Generate { root, out, config } => {
let config = load_config(config)?;
let model = analyze_project(root, &config)?;
generate_docs(&model, out)?;
generate_docs(&model, out, cli.verbose)?;
}
Commands::Check { root, config } => {
let config = load_config(config)?;
@@ -102,12 +102,31 @@ fn init_project(root: &str, out: &str) -> Result<()> {
std::fs::create_dir_all(out_path.join("files"))
.map_err(|e| anyhow::anyhow!("Failed to create files directory: {}", e))?;
// Create layout.md file
let layout_md_path = out_path.join("layout.md");
let layout_md_content = r#"# Repository layout
<!-- MANUAL:BEGIN -->
## Manual overrides
- `src/app/` — <FILL_MANUALLY>
<!-- MANUAL:END -->
---
## Detected structure
<!-- ARCHDOC:BEGIN section=layout_detected -->
> Generated. Do not edit inside this block.
<!-- ARCHDOC:END section=layout_detected -->
"#;
std::fs::write(&layout_md_path, layout_md_content)
.map_err(|e| anyhow::anyhow!("Failed to create layout.md: {}", e))?;
// Create default ARCHITECTURE.md template
let architecture_md_content = r#"# ARCHITECTURE — New Project
let architecture_md_content = r#"# ARCHITECTURE — <PROJECT_NAME>
<!-- MANUAL:BEGIN -->
## Project summary
**Name:** New Project
**Name:** <PROJECT_NAME>
**Description:** <FILL_MANUALLY: what this project does in 37 lines>
## Key decisions (manual)
@@ -120,8 +139,8 @@ fn init_project(root: &str, out: &str) -> Result<()> {
---
## Document metadata
- **Created:** 2026-01-25
- **Updated:** 2026-01-25
- **Created:** <AUTO_ON_INIT: YYYY-MM-DD>
- **Updated:** <AUTO_ON_CHANGE: YYYY-MM-DD>
- **Generated by:** archdoc (cli) v0.1
---
@@ -129,7 +148,7 @@ fn init_project(root: &str, out: &str) -> Result<()> {
## Rails / Tooling
<!-- ARCHDOC:BEGIN section=rails -->
> Generated. Do not edit inside this block.
<AUTO: rails summary + links to config files>
<!-- ARCHDOC:END section=rails -->
---
@@ -137,7 +156,7 @@ fn init_project(root: &str, out: &str) -> Result<()> {
## Repository layout (top-level)
<!-- ARCHDOC:BEGIN section=layout -->
> Generated. Do not edit inside this block.
<AUTO: table of top-level folders + heuristic purpose + link to layout.md>
<!-- ARCHDOC:END section=layout -->
---
@@ -145,7 +164,7 @@ fn init_project(root: &str, out: &str) -> Result<()> {
## Modules index
<!-- ARCHDOC:BEGIN section=modules_index -->
> Generated. Do not edit inside this block.
<AUTO: table modules + deps counts + links to module docs>
<!-- ARCHDOC:END section=modules_index -->
---
@@ -153,7 +172,7 @@ fn init_project(root: &str, out: &str) -> Result<()> {
## Critical dependency points
<!-- ARCHDOC:BEGIN section=critical_points -->
> Generated. Do not edit inside this block.
<AUTO: top fan-in/out symbols + cycles>
<!-- ARCHDOC:END section=critical_points -->
---
@@ -295,7 +314,7 @@ fn sanitize_filename(filename: &str) -> String {
.collect()
}
fn generate_docs(model: &ProjectModel, out: &str) -> Result<()> {
fn generate_docs(model: &ProjectModel, out: &str, verbose: bool) -> Result<()> {
// TODO: Implement documentation generation
println!("Generating docs to {}", out);
@@ -340,11 +359,87 @@ fn generate_docs(model: &ProjectModel, out: &str) -> Result<()> {
}
}
for (_file_id, file_doc) in &model.files {
// Create individual documentation files for files and symbols
for (file_id, file_doc) in &model.files {
let file_doc_path = files_path.join(format!("{}.md", sanitize_filename(&file_doc.path)));
let file_content = format!("# File: {}\n\nTODO: Add file documentation\n", file_doc.path);
// Create file documentation with symbol sections
let mut file_content = format!("# File: {}\n\n", file_doc.path);
file_content.push_str(&format!("- **Module:** {}\n", file_doc.module_id));
file_content.push_str(&format!("- **Defined symbols:** {}\n", file_doc.symbols.len()));
file_content.push_str(&format!("- **Imports:** {}\n\n", file_doc.imports.len()));
file_content.push_str("<!-- MANUAL:BEGIN -->\n");
file_content.push_str("## File intent (manual)\n");
file_content.push_str("<FILL_MANUALLY>\n");
file_content.push_str("<!-- MANUAL:END -->\n\n");
file_content.push_str("---\n\n");
file_content.push_str("## Imports & file-level dependencies\n");
file_content.push_str("<!-- ARCHDOC:BEGIN section=file_imports -->\n");
file_content.push_str("> Generated. Do not edit inside this block.\n");
for import in &file_doc.imports {
file_content.push_str(&format!("- {}\n", import));
}
file_content.push_str("<!-- ARCHDOC:END section=file_imports -->\n\n");
file_content.push_str("---\n\n");
file_content.push_str("## Symbols index\n");
file_content.push_str("<!-- ARCHDOC:BEGIN section=symbols_index -->\n");
file_content.push_str("> Generated. Do not edit inside this block.\n");
for symbol_id in &file_doc.symbols {
if let Some(symbol) = model.symbols.get(symbol_id) {
file_content.push_str(&format!("- [{}]({}#{})\n", symbol.qualname, sanitize_filename(&file_doc.path), symbol_id));
}
}
file_content.push_str("<!-- ARCHDOC:END section=symbols_index -->\n\n");
file_content.push_str("---\n\n");
file_content.push_str("## Symbol details\n");
// Add symbol markers for each symbol
for symbol_id in &file_doc.symbols {
if let Some(_symbol) = model.symbols.get(symbol_id) {
if verbose {
println!("Adding symbol marker for {} in {}", symbol_id, file_doc_path.display());
}
file_content.push_str(&format!("\n<!-- ARCHDOC:BEGIN symbol id={} -->\n", symbol_id));
file_content.push_str("<!-- AUTOGENERATED SYMBOL CONTENT WILL BE INSERTED HERE -->\n");
file_content.push_str(&format!("<!-- ARCHDOC:END symbol id={} -->\n", symbol_id));
}
}
if verbose {
println!("Writing file content to {}: {} chars", file_doc_path.display(), file_content.len());
// Show last 500 characters to see if symbol markers are there
let len = file_content.len();
let start = if len > 500 { len - 500 } else { 0 };
println!("Last 500 chars: {}", &file_content[start..]);
}
std::fs::write(&file_doc_path, file_content)
.map_err(|e| anyhow::anyhow!("Failed to create file doc {}: {}", file_doc_path.display(), e))?;
// Update each symbol section in the file
for symbol_id in &file_doc.symbols {
if let Some(_symbol) = model.symbols.get(symbol_id) {
match renderer.render_symbol_details(model, symbol_id) {
Ok(content) => {
if verbose {
println!("Updating symbol section for {} in {}", symbol_id, file_doc_path.display());
}
if let Err(e) = writer.update_symbol_section(&file_doc_path, symbol_id, &content) {
eprintln!("Warning: Failed to update symbol section for {}: {}", symbol_id, e);
}
}
Err(e) => {
eprintln!("Warning: Failed to render symbol details for {}: {}", symbol_id, e);
}
}
}
}
}
// Render and update each section individually
@@ -373,7 +468,7 @@ fn generate_docs(model: &ProjectModel, out: &str) -> Result<()> {
}
}
// Update layout section
// Update layout section in ARCHITECTURE.md
match renderer.render_layout_section(model) {
Ok(content) => {
if let Err(e) = writer.update_file_with_markers(&output_path, &content, "layout") {
@@ -409,6 +504,20 @@ fn generate_docs(model: &ProjectModel, out: &str) -> Result<()> {
}
}
// Update layout.md file
let layout_md_path = out_path.join("layout.md");
match renderer.render_layout_md(model) {
Ok(content) => {
// Write the full content to layout.md
if let Err(e) = std::fs::write(&layout_md_path, &content) {
eprintln!("Warning: Failed to write layout.md: {}", e);
}
}
Err(e) => {
eprintln!("Warning: Failed to render layout.md: {}", e);
}
}
Ok(())
}