#!/usr/bin/env python3importsubprocessimportargparseimportosimportsysimporttimefrompathlibimportPathlua_filters=["include-files.lua","preserve-ref.lua","citations.lua","pagebreak.lua","newline.lua","caption-code-blocks.lua","plantuml.lua","mermaid.lua","align-figures.lua","align-tables.lua","callouts.lua",]template=Path(__file__).parent/"template/bergfink.typst"filters_dir=Path(__file__).parent/"filters"defmain():parser=argparse.ArgumentParser(description="Convert Markdown to PDF using Pandoc and Typst")parser.add_argument("input",nargs="+",help="Input Markdown files")parser.add_argument("--output","-o",help="Output PDF file (defaults to input.pdf)")parser.add_argument("--metadata","-m",help="Metadata file")parser.add_argument("--typ",nargs="?",const=True,help="Output typst file (default: input.typ)")parser.add_argument("--config","-c",help="Custom Typst configuration file")parser.add_argument("--watch","-w",action="store_true",help="Watch input files for changes")args=parser.parse_args()# Resolve paths to absolute before changing directoryinput_paths=[Path(i).resolve()foriinargs.input]ifargs.typ:ifisinstance(args.typ,str):args.output=args.typelse:args.output=str(input_paths[0].with_suffix(".typ"))elifnotargs.output:args.output=str(input_paths[0].with_suffix(".pdf"))output_path=Path(args.output).resolve()metadata_path=Path(args.metadata).resolve()ifargs.metadataelseNoneconfig_path=Path(args.config).resolve()ifargs.configelseNone# Change to directory of the first input fileos.chdir(input_paths[0].parent)cmd=["pandoc",*[str(p)forpininput_paths],"-o",str(output_path),"--from","markdown","--template",str(template),]ifoutput_path.suffix==".typ":cmd.extend(["--to","typst"])else:cmd.extend(["--pdf-engine","typst"])iflua_filters:forlua_filterinlua_filters:cmd.extend(["--lua-filter",str(filters_dir/lua_filter)])ifmetadata_path:cmd.extend(["--metadata-file",str(metadata_path)])ifconfig_path:try:# Use relative path to avoid Typst root issues when include is used in templaterel_config_path=os.path.relpath(config_path,input_paths[0].parent)cmd.extend(["-V",f"user-config={rel_config_path}"])exceptValueError:cmd.extend(["-V",f"user-config={config_path}"])defrun_conversion():try:print(f"Running conversion...")subprocess.run(cmd,check=True)print("Done.")returnTrueexceptsubprocess.CalledProcessErrorase:print(f"Error: {e}")returnFalseifnotargs.watch:ifnotrun_conversion():sys.exit(1)else:run_conversion()files_to_watch=list(input_paths)ifmetadata_pathandmetadata_path.exists():files_to_watch.append(metadata_path)ifconfig_pathandconfig_path.exists():files_to_watch.append(config_path)print(f"Watching for changes in {[p.nameforpinfiles_to_watch]}... (Ctrl+C to stop)")last_mtimes={p:p.stat().st_mtimeforpinfiles_to_watchifp.exists()}try:whileTrue:time.sleep(1)changed=Falseforpinfiles_to_watch:ifnotp.exists():continuemtime=p.stat().st_mtimeifpnotinlast_mtimesormtime>last_mtimes[p]:last_mtimes[p]=mtimechanged=Trueifchanged:print("\nChange detected, re-running conversion...")run_conversion()exceptKeyboardInterrupt:print("\nStopped watching.")sys.exit(0)if__name__=="__main__":main()