14
14
import logging
15
15
import re
16
16
from dataclasses import dataclass
17
+ from dataclasses import field
17
18
from difflib import Differ
18
19
from functools import reduce
19
20
from itertools import islice
37
38
)
38
39
logger = logging .getLogger (__name__ )
39
40
40
- ProcessingFunc = Callable [[str ], str ]
41
-
42
41
43
42
def compose (* functions : ProcessingFunc ) -> ProcessingFunc :
44
43
"""Compose multiple processing functions into a single function."""
@@ -270,50 +269,63 @@ def preview_changes(original: str, processed: str) -> None:
270
269
print_change_group (group )
271
270
272
271
273
- def process_file (
274
- input : str = "README.md" ,
275
- output : str = "docs/index.md" ,
276
- processors : list [ProcessingFunc ] | None = None ,
277
- preview : bool = True ,
278
- description : str | None = None ,
279
- ) -> bool :
280
- """
281
- Process a file with given processing functions.
282
-
283
- Args:
284
- input: Path to the input file
285
- output: Path where the processed file will be saved
286
- processors: List of processing functions to apply
287
- preview: Whether to show a preview of changes
288
- description: Optional description for status message
289
-
290
- Returns:
291
- bool: True if processing was successful, False otherwise
292
- """
293
- status_msg = f"[bold green]Processing { description or input } ..."
294
- with console .status (status_msg ) as status :
295
- input_path = Path (input )
296
- output_path = Path (output )
297
-
298
- content = read_file (input_path )
299
- if content is None :
300
- return False
301
-
302
- original_content = content
303
-
304
- try :
305
- for proc in track (processors , description = "Applying processors" ):
306
- status .update (f"[bold green]Running { proc .__name__ } ..." )
307
- content = proc (content )
308
-
309
- if preview :
310
- preview_changes (original_content , content )
311
-
312
- return write_file (output_path , content )
313
-
314
- except Exception as e :
315
- console .print (f"[red]Error during processing:[/red] { e } " )
316
- return False
272
+ @dataclass
273
+ class File :
274
+ """A file to be processed."""
275
+
276
+ input_path : Path | str
277
+ output_path : Path | str
278
+ repo_url : str = "https://github.com/joshuadavidthomas/django-language-server"
279
+ content : str = ""
280
+ processors : list [ProcessingFunc ] = field (default_factory = list )
281
+
282
+ def __post_init__ (self ):
283
+ self .input_path = Path (self .input_path )
284
+ self .output_path = Path (self .output_path )
285
+
286
+ def process (self , preview : bool = True ) -> bool :
287
+ """Process the file with given processing functions."""
288
+ with console .status (
289
+ f"[bold green]Processing { self .input_path } → { self .output_path } ..."
290
+ ) as status :
291
+ content = read_file (self .input_path )
292
+ if content is None :
293
+ return False
294
+
295
+ self .content = content
296
+ original_content = content
297
+
298
+ try :
299
+ for proc in track (self .processors , description = "Applying processors" ):
300
+ status .update (f"[bold green]Running { proc .__name__ } ..." )
301
+ content = proc (content , self )
302
+
303
+ if preview :
304
+ preview_changes (original_content , content )
305
+
306
+ return write_file (self .output_path , content )
307
+
308
+ except Exception as e :
309
+ console .print (f"[red]Error during processing:[/red] { e } " )
310
+ return False
311
+
312
+
313
+ ProcessingFunc = Callable [[str , File ], str ]
314
+
315
+
316
+ def add_generated_warning (content : str , file : File ) -> str :
317
+ """Add a warning comment indicating the file is auto-generated."""
318
+ script_path = Path (__file__ ).relative_to (Path (__file__ ).parent .parent )
319
+ warning = [
320
+ "<!--" ,
321
+ "THIS FILE IS AUTO-GENERATED" ,
322
+ "DO NOT EDIT THIS FILE DIRECTLY" ,
323
+ f"Generated via { script_path } from { file .input_path } " ,
324
+ "-->" ,
325
+ "" ,
326
+ "" ,
327
+ ]
328
+ return "\n " .join (warning ) + content
317
329
318
330
319
331
def add_frontmatter (
@@ -345,7 +357,7 @@ def add_frontmatter(
345
357
Content here
346
358
"""
347
359
348
- def processor (content : str ) -> str :
360
+ def processor (content : str , _file : File ) -> str :
349
361
# Remove existing frontmatter if present
350
362
content_without_frontmatter = re .sub (
351
363
r"^---\n.*?\n---\n" , "" , content , flags = re .DOTALL
@@ -371,7 +383,7 @@ def processor(content: str) -> str:
371
383
return processor
372
384
373
385
374
- def convert_admonitions (content : str ) -> str :
386
+ def convert_admonitions (content : str , _file : File ) -> str :
375
387
"""
376
388
Convert GitHub-style admonitions to Material for MkDocs-style admonitions.
377
389
@@ -431,106 +443,83 @@ def process_match(match: re.Match[str]) -> str:
431
443
return re .sub (pattern , process_match , content )
432
444
433
445
434
- def convert_repo_links (repo_url : str ) -> ProcessingFunc :
435
- """
436
- Convert relative repository links to absolute URLs.
446
+ def convert_repo_links (content : str , file : File ) -> str :
447
+ """Convert relative repository links to absolute URLs."""
437
448
438
- Args:
439
- repo_url: The base repository URL (e.g., 'https://github.com/username/repo')
449
+ def replace_link (match : re .Match [str ]) -> str :
450
+ text = match .group (1 )
451
+ path = match .group (2 )
440
452
441
- Returns:
442
- A processor function that converts relative links to absolute URLs
443
-
444
- Example:
445
- Input:
446
- See the [`LICENSE`](LICENSE) file for more information.
447
- Check the [Neovim](/docs/editors/neovim.md) guide.
448
- Open an [issue](../../issues/new) to report bugs.
449
-
450
- Output:
451
- See the [`LICENSE`](https://github.com/username/repo/blob/main/LICENSE) file for more information.
452
- Check the [Neovim](editors/neovim.md) guide.
453
- Open an [issue](https://github.com/username/repo/issues/new) to report bugs.
454
- """
455
-
456
- def processor (content : str ) -> str :
457
- def replace_link (match : re .Match [str ]) -> str :
458
- text = match .group (1 )
459
- path = match .group (2 )
453
+ # Skip anchor links
454
+ if path .startswith ("#" ):
455
+ return match .group (0 )
460
456
461
- # Skip anchor links
462
- if path .startswith ("#" ):
463
- return match .group (0 )
457
+ # Skip already absolute URLs
458
+ if path .startswith (( "http://" , "https://" ) ):
459
+ return match .group (0 )
464
460
465
- # Skip already absolute URLs
466
- if path .startswith (("http://" , "https://" )):
467
- return match .group (0 )
461
+ # Handle docs directory links
462
+ if path .startswith (("/docs/" , "docs/" )):
463
+ # Remove /docs/ or docs/ prefix and .md extension
464
+ clean_path = path .removeprefix ("/docs/" ).removeprefix ("docs/" )
465
+ return f"[{ text } ]({ clean_path } )"
468
466
469
- # Handle docs directory links
470
- if path .startswith (("/docs/" , "docs/" )):
471
- # Remove /docs/ or docs/ prefix and .md extension
472
- clean_path = path .removeprefix ("/docs/" ).removeprefix ("docs/" )
473
- return f"[{ text } ]({ clean_path } )"
467
+ # Handle relative paths with ../ or ./
468
+ if "../" in path or "./" in path :
469
+ # Special handling for GitHub-specific paths
470
+ if "issues/" in path or "pulls/" in path :
471
+ clean_path = path .replace ("../" , "" ).replace ("./" , "" )
472
+ return f"[{ text } ]({ file .repo_url } /{ clean_path } )"
474
473
475
- # Handle relative paths with ../ or ./
476
- if "../" in path or "./" in path :
477
- # Special handling for GitHub-specific paths
478
- if "issues/" in path or "pulls/" in path :
479
- clean_path = path .replace ("../" , "" ).replace ("./" , "" )
480
- return f"[{ text } ]({ repo_url } /{ clean_path } )"
474
+ # Handle root-relative paths
475
+ if path .startswith ("/" ):
476
+ path = path .removeprefix ("/" )
481
477
482
- # Handle root-relative paths
483
- if path .startswith ("/" ):
484
- path = path .removeprefix ("/" )
478
+ # Remove ./ if present
479
+ path = path .removeprefix ("./" )
485
480
486
- # Remove ./ if present
487
- path = path .removeprefix ("./" )
481
+ # Construct the full URL for repository files
482
+ full_url = f"{ file .repo_url .rstrip ('/' )} /blob/main/{ path } "
483
+ return f"[{ text } ]({ full_url } )"
488
484
489
- # Construct the full URL for repository files
490
- full_url = f"{ repo_url .rstrip ('/' )} /blob/main/{ path } "
491
- return f"[{ text } ]({ full_url } )"
492
-
493
- # Match markdown links: [text](url)
494
- pattern = r"\[((?:[^][]|\[[^]]*\])*)\]\(([^)]+)\)"
495
- return re .sub (pattern , replace_link , content )
496
-
497
- processor .__name__ = "convert_repo_links"
498
- return processor
485
+ # Match markdown links: [text](url)
486
+ pattern = r"\[((?:[^][]|\[[^]]*\])*)\]\(([^)]+)\)"
487
+ return re .sub (pattern , replace_link , content )
499
488
500
489
501
490
def main ():
502
491
"""Process documentation files."""
503
492
console .print ("[bold blue]Documentation Processor[/bold blue]" )
504
493
494
+ # Common processors
505
495
common_processors = [
496
+ add_generated_warning ,
506
497
convert_admonitions ,
507
- convert_repo_links (
508
- "https://github.com/joshuadavidthomas/django-language-server"
509
- ),
498
+ convert_repo_links ,
510
499
]
511
500
512
- readme_success = process_file (
513
- input = "README.md" ,
514
- output = "docs/index.md" ,
501
+ readme = File (
502
+ input_path = "README.md" ,
503
+ output_path = "docs/index.md" ,
515
504
processors = [
516
- add_frontmatter ({"title" : "Home" }),
517
505
* common_processors ,
506
+ add_frontmatter ({"title" : "Home" }),
518
507
],
519
- preview = True ,
520
- description = "README.md → docs/index.md" ,
521
508
)
522
509
523
- nvim_success = process_file (
524
- input = "editors/nvim/README.md" ,
525
- output = "docs/editors/neovim.md" ,
510
+ nvim = File (
511
+ input_path = "editors/nvim/README.md" ,
512
+ output_path = "docs/editors/neovim.md" ,
526
513
processors = [
527
- add_frontmatter ({"title" : "Neovim" }),
528
514
* common_processors ,
515
+ add_frontmatter ({"title" : "Neovim" }),
529
516
],
530
- preview = True ,
531
- description = "Neovim docs → docs/editors/neovim.md" ,
532
517
)
533
518
519
+ # Process files
520
+ readme_success = readme .process (preview = True )
521
+ nvim_success = nvim .process (preview = True )
522
+
534
523
if readme_success and nvim_success :
535
524
console .print ("\n [green]✨ All files processed successfully![/green]" )
536
525
else :
0 commit comments