apex-ruby 1.0.7 → 1.0.9

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/ext/apex_ext/apex_src/CHANGELOG.md +69 -0
  3. data/ext/apex_ext/apex_src/CMakeLists.txt +2 -1
  4. data/ext/apex_ext/apex_src/Formula/apex.rb +2 -2
  5. data/ext/apex_ext/apex_src/Package.swift +14 -2
  6. data/ext/apex_ext/apex_src/README.md +12 -9
  7. data/ext/apex_ext/apex_src/VERSION +1 -1
  8. data/ext/apex_ext/apex_src/cli/main.c +625 -98
  9. data/ext/apex_ext/apex_src/ial.html +24 -0
  10. data/ext/apex_ext/apex_src/include/apex/apex.h +57 -7
  11. data/ext/apex_ext/apex_src/include/apex/ast_markdown.h +3 -0
  12. data/ext/apex_ext/apex_src/include/apex/module.modulemap +8 -0
  13. data/ext/apex_ext/apex_src/include/apexc.h +6 -0
  14. data/ext/apex_ext/apex_src/include/module.modulemap +4 -0
  15. data/ext/apex_ext/apex_src/man/apex-config.5 +8 -2
  16. data/ext/apex_ext/apex_src/man/apex-plugins.7 +13 -13
  17. data/ext/apex_ext/apex_src/man/apex.1 +150 -442
  18. data/ext/apex_ext/apex_src/man/apex.1.md +13 -0
  19. data/ext/apex_ext/apex_src/src/_README.md +3 -1
  20. data/ext/apex_ext/apex_src/src/apex.c +151 -6
  21. data/ext/apex_ext/apex_src/src/ast_terminal.c +459 -8
  22. data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +6 -6
  23. data/ext/apex_ext/apex_src/src/extensions/callouts.c +1 -1
  24. data/ext/apex_ext/apex_src/src/extensions/citations.c +24 -12
  25. data/ext/apex_ext/apex_src/src/extensions/critic.c +14 -6
  26. data/ext/apex_ext/apex_src/src/extensions/emoji.c +2 -2
  27. data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1 -1
  28. data/ext/apex_ext/apex_src/src/extensions/header_ids.c +19 -6
  29. data/ext/apex_ext/apex_src/src/extensions/ial.c +25 -13
  30. data/ext/apex_ext/apex_src/src/extensions/includes.c +7 -7
  31. data/ext/apex_ext/apex_src/src/extensions/index.c +19 -7
  32. data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.c +2 -2
  33. data/ext/apex_ext/apex_src/src/extensions/insert.c +1 -1
  34. data/ext/apex_ext/apex_src/src/extensions/math.c +11 -2
  35. data/ext/apex_ext/apex_src/src/extensions/metadata.c +46 -0
  36. data/ext/apex_ext/apex_src/src/extensions/metadata.h +12 -0
  37. data/ext/apex_ext/apex_src/src/html_renderer.c +2 -2
  38. data/ext/apex_ext/apex_src/src/plugins.c +97 -55
  39. data/ext/apex_ext/apex_src/src/plugins.h +0 -10
  40. data/ext/apex_ext/apex_src/src/pretty_html.c +1 -1
  41. data/ext/apex_ext/apex_src/tests/fixtures/metadata/mmd-metadata.md +5 -0
  42. data/ext/apex_ext/apex_src/tests/fixtures/metadata/pandoc-meta.md +4 -0
  43. data/ext/apex_ext/apex_src/tests/fixtures/metadata/yaml-frontmatter.md +6 -0
  44. data/ext/apex_ext/apex_src/tests/metadata_cli_test.sh +119 -0
  45. data/ext/apex_ext/apex_src/tests/test_custom_plugins.c +78 -0
  46. data/ext/apex_ext/apex_src/tests/test_extensions.c +27 -0
  47. data/ext/apex_ext/apex_src/tests/test_metadata.c +42 -0
  48. data/ext/apex_ext/apex_src/tests/test_output.c +83 -0
  49. data/ext/apex_ext/apex_src/tests/test_runner.c +4 -1
  50. data/lib/apex/version.rb +1 -1
  51. metadata +10 -2
@@ -18,6 +18,179 @@
18
18
  #include <sys/ioctl.h>
19
19
  #include <limits.h>
20
20
 
21
+ static char *read_file(const char *filename, size_t *len);
22
+
23
+ /**
24
+ * Tracks which apex_options fields were set explicitly from argv so that
25
+ * merged config/document metadata cannot override them after apex_apply_metadata_to_options.
26
+ */
27
+ typedef struct apex_cli_option_mask {
28
+ bool mode;
29
+ bool output_format;
30
+ bool xhtml;
31
+ bool strict_xhtml;
32
+ bool theme_name;
33
+ bool enable_plugins;
34
+ bool enable_tables;
35
+ bool enable_footnotes;
36
+ bool enable_smart_typography;
37
+ bool enable_math;
38
+ bool enable_file_includes;
39
+ bool hardbreaks;
40
+ bool standalone;
41
+ bool embed_stylesheet;
42
+ bool document_title;
43
+ bool pretty;
44
+ bool critic;
45
+ bool id_format;
46
+ bool generate_header_ids;
47
+ bool header_anchors;
48
+ bool relaxed_tables;
49
+ bool per_cell_alignment;
50
+ bool caption_position;
51
+ bool code_highlighter;
52
+ bool code_highlight_theme;
53
+ bool code_line_numbers;
54
+ bool highlight_language_only;
55
+ bool allow_alpha_lists;
56
+ bool allow_mixed_list_markers;
57
+ bool unsafe;
58
+ bool enable_sup_sub;
59
+ bool enable_divs;
60
+ bool enable_definition_lists;
61
+ bool enable_spans;
62
+ bool enable_autolink;
63
+ bool enable_strikethrough;
64
+ bool obfuscate_emails;
65
+ bool enable_aria;
66
+ bool enable_wiki_links;
67
+ bool enable_emoji_autocorrect;
68
+ bool enable_widont;
69
+ bool code_is_poetry;
70
+ bool enable_markdown_in_html;
71
+ bool random_footnote_ids;
72
+ bool enable_hashtags;
73
+ bool style_hashtags;
74
+ bool proofreader;
75
+ bool hr_page_break;
76
+ bool title_from_h1;
77
+ bool page_break_before_footnotes;
78
+ bool wikilink_space;
79
+ bool wikilink_extension;
80
+ bool wikilink_sanitize;
81
+ bool enable_metadata_transforms;
82
+ bool embed_images;
83
+ bool enable_image_captions;
84
+ bool title_captions_only;
85
+ bool base_directory;
86
+ bool bibliography;
87
+ bool csl_file;
88
+ bool suppress_bibliography;
89
+ bool link_citations;
90
+ bool show_tooltips;
91
+ bool indices;
92
+ bool suppress_index;
93
+ bool stylesheet;
94
+ } apex_cli_option_mask;
95
+
96
+ static void apex_cli_restore_argv_options(apex_options *opts,
97
+ const apex_options *snap,
98
+ const apex_cli_option_mask *m) {
99
+ if (m->mode) opts->mode = snap->mode;
100
+ if (m->output_format) opts->output_format = snap->output_format;
101
+ if (m->xhtml) opts->xhtml = snap->xhtml;
102
+ if (m->strict_xhtml) opts->strict_xhtml = snap->strict_xhtml;
103
+ if (m->theme_name) opts->theme_name = snap->theme_name;
104
+ if (m->enable_plugins) opts->enable_plugins = snap->enable_plugins;
105
+ if (m->enable_tables) opts->enable_tables = snap->enable_tables;
106
+ if (m->enable_footnotes) opts->enable_footnotes = snap->enable_footnotes;
107
+ if (m->enable_smart_typography) opts->enable_smart_typography = snap->enable_smart_typography;
108
+ if (m->enable_math) opts->enable_math = snap->enable_math;
109
+ if (m->enable_file_includes) opts->enable_file_includes = snap->enable_file_includes;
110
+ if (m->hardbreaks) opts->hardbreaks = snap->hardbreaks;
111
+ if (m->standalone) opts->standalone = snap->standalone;
112
+ if (m->embed_stylesheet) opts->embed_stylesheet = snap->embed_stylesheet;
113
+ if (m->document_title) opts->document_title = snap->document_title;
114
+ if (m->pretty) opts->pretty = snap->pretty;
115
+ if (m->critic) {
116
+ opts->enable_critic_markup = snap->enable_critic_markup;
117
+ opts->critic_mode = snap->critic_mode;
118
+ }
119
+ if (m->id_format) opts->id_format = snap->id_format;
120
+ if (m->generate_header_ids) opts->generate_header_ids = snap->generate_header_ids;
121
+ if (m->header_anchors) opts->header_anchors = snap->header_anchors;
122
+ if (m->relaxed_tables) opts->relaxed_tables = snap->relaxed_tables;
123
+ if (m->per_cell_alignment) opts->per_cell_alignment = snap->per_cell_alignment;
124
+ if (m->caption_position) opts->caption_position = snap->caption_position;
125
+ if (m->code_highlighter) opts->code_highlighter = snap->code_highlighter;
126
+ if (m->code_highlight_theme) opts->code_highlight_theme = snap->code_highlight_theme;
127
+ if (m->code_line_numbers) opts->code_line_numbers = snap->code_line_numbers;
128
+ if (m->highlight_language_only) opts->highlight_language_only = snap->highlight_language_only;
129
+ if (m->allow_alpha_lists) opts->allow_alpha_lists = snap->allow_alpha_lists;
130
+ if (m->allow_mixed_list_markers) opts->allow_mixed_list_markers = snap->allow_mixed_list_markers;
131
+ if (m->unsafe) opts->unsafe = snap->unsafe;
132
+ if (m->enable_sup_sub) opts->enable_sup_sub = snap->enable_sup_sub;
133
+ if (m->enable_divs) opts->enable_divs = snap->enable_divs;
134
+ if (m->enable_definition_lists) opts->enable_definition_lists = snap->enable_definition_lists;
135
+ if (m->enable_spans) opts->enable_spans = snap->enable_spans;
136
+ if (m->enable_autolink) opts->enable_autolink = snap->enable_autolink;
137
+ if (m->enable_strikethrough) opts->enable_strikethrough = snap->enable_strikethrough;
138
+ if (m->obfuscate_emails) opts->obfuscate_emails = snap->obfuscate_emails;
139
+ if (m->enable_aria) opts->enable_aria = snap->enable_aria;
140
+ if (m->enable_wiki_links) opts->enable_wiki_links = snap->enable_wiki_links;
141
+ if (m->enable_emoji_autocorrect) opts->enable_emoji_autocorrect = snap->enable_emoji_autocorrect;
142
+ if (m->enable_widont) opts->enable_widont = snap->enable_widont;
143
+ if (m->code_is_poetry) {
144
+ opts->code_is_poetry = snap->code_is_poetry;
145
+ opts->highlight_language_only = snap->highlight_language_only;
146
+ }
147
+ if (m->enable_markdown_in_html) opts->enable_markdown_in_html = snap->enable_markdown_in_html;
148
+ if (m->random_footnote_ids) opts->random_footnote_ids = snap->random_footnote_ids;
149
+ if (m->enable_hashtags) opts->enable_hashtags = snap->enable_hashtags;
150
+ if (m->style_hashtags) opts->style_hashtags = snap->style_hashtags;
151
+ if (m->proofreader) {
152
+ opts->proofreader_mode = snap->proofreader_mode;
153
+ opts->enable_critic_markup = snap->enable_critic_markup;
154
+ opts->critic_mode = snap->critic_mode;
155
+ }
156
+ if (m->hr_page_break) opts->hr_page_break = snap->hr_page_break;
157
+ if (m->title_from_h1) opts->title_from_h1 = snap->title_from_h1;
158
+ if (m->page_break_before_footnotes) opts->page_break_before_footnotes = snap->page_break_before_footnotes;
159
+ if (m->wikilink_space) opts->wikilink_space = snap->wikilink_space;
160
+ if (m->wikilink_extension) opts->wikilink_extension = snap->wikilink_extension;
161
+ if (m->wikilink_sanitize) opts->wikilink_sanitize = snap->wikilink_sanitize;
162
+ if (m->enable_metadata_transforms) opts->enable_metadata_transforms = snap->enable_metadata_transforms;
163
+ if (m->embed_images) opts->embed_images = snap->embed_images;
164
+ if (m->enable_image_captions) opts->enable_image_captions = snap->enable_image_captions;
165
+ if (m->title_captions_only) {
166
+ opts->title_captions_only = snap->title_captions_only;
167
+ opts->enable_image_captions = snap->enable_image_captions;
168
+ }
169
+ if (m->base_directory) opts->base_directory = snap->base_directory;
170
+ if (m->bibliography) {
171
+ opts->bibliography_files = snap->bibliography_files;
172
+ opts->enable_citations = snap->enable_citations;
173
+ }
174
+ if (m->csl_file) {
175
+ opts->csl_file = snap->csl_file;
176
+ opts->enable_citations = snap->enable_citations;
177
+ }
178
+ if (m->suppress_bibliography) opts->suppress_bibliography = snap->suppress_bibliography;
179
+ if (m->link_citations) opts->link_citations = snap->link_citations;
180
+ if (m->show_tooltips) opts->show_tooltips = snap->show_tooltips;
181
+ if (m->indices) {
182
+ opts->enable_indices = snap->enable_indices;
183
+ opts->enable_mmark_index_syntax = snap->enable_mmark_index_syntax;
184
+ opts->enable_textindex_syntax = snap->enable_textindex_syntax;
185
+ opts->enable_leanpub_index_syntax = snap->enable_leanpub_index_syntax;
186
+ }
187
+ if (m->suppress_index) opts->suppress_index = snap->suppress_index;
188
+ if (m->stylesheet) {
189
+ opts->stylesheet_paths = snap->stylesheet_paths;
190
+ opts->stylesheet_count = snap->stylesheet_count;
191
+ }
192
+ }
193
+
21
194
  /* Remote plugin directory helpers (from plugins_remote.c) */
22
195
  typedef struct apex_remote_plugin apex_remote_plugin;
23
196
  typedef struct apex_remote_plugin_list apex_remote_plugin_list;
@@ -479,6 +652,192 @@ static void cli_collect_installed_from_root(const char *root,
479
652
  closedir(d);
480
653
  }
481
654
 
655
+ /**
656
+ * Merge document metadata from one or more files (later files override).
657
+ * Sets *out to merged metadata (may be NULL if no blocks found in any file).
658
+ * Returns 0 on success, non-zero if a file could not be read.
659
+ */
660
+ static int apex_cli_merge_doc_metadata_from_files(apex_mode_t mode,
661
+ char **paths,
662
+ size_t n,
663
+ apex_metadata_item **out) {
664
+ apex_metadata_item *acc = NULL;
665
+ *out = NULL;
666
+ for (size_t i = 0; i < n; i++) {
667
+ if (!paths || !paths[i]) continue;
668
+ size_t len = 0;
669
+ char *raw = read_file(paths[i], &len);
670
+ if (!raw) {
671
+ apex_free_metadata(acc);
672
+ return 1;
673
+ }
674
+ char *copy = malloc(len + 1);
675
+ if (!copy) {
676
+ free(raw);
677
+ apex_free_metadata(acc);
678
+ return 1;
679
+ }
680
+ memcpy(copy, raw, len + 1);
681
+ free(raw);
682
+ char *ptr = copy;
683
+ apex_metadata_item *chunk = apex_extract_metadata_for_mode(&ptr, mode);
684
+ free(copy);
685
+ if (!chunk) continue;
686
+ apex_metadata_item *merged = apex_merge_metadata(acc, chunk, NULL);
687
+ if (acc) apex_free_metadata(acc);
688
+ apex_free_metadata(chunk);
689
+ acc = merged;
690
+ }
691
+ *out = acc;
692
+ return 0;
693
+ }
694
+
695
+ /**
696
+ * Print version, merged config (global + project + --meta-file + --meta), and plugin resolution.
697
+ */
698
+ static void apex_cli_print_info(FILE *out,
699
+ const apex_options *options,
700
+ bool plugins_cli_override,
701
+ bool plugins_cli_value,
702
+ const char *meta_file,
703
+ apex_metadata_item *cmdline_metadata) {
704
+ fprintf(out, "version: %s\n\n", apex_version_string());
705
+
706
+ char *global_config_path = apex_cli_find_global_config();
707
+ char *project_config_path = apex_cli_find_project_config(options);
708
+
709
+ apex_metadata_item *global_config_meta = NULL;
710
+ apex_metadata_item *project_config_meta = NULL;
711
+ apex_metadata_item *explicit_file_meta = NULL;
712
+
713
+ if (global_config_path) {
714
+ global_config_meta = apex_load_metadata_from_file(global_config_path);
715
+ }
716
+ if (project_config_path) {
717
+ project_config_meta = apex_load_metadata_from_file(project_config_path);
718
+ }
719
+ if (meta_file) {
720
+ explicit_file_meta = apex_load_metadata_from_file(meta_file);
721
+ }
722
+
723
+ apex_metadata_item *merged_config = NULL;
724
+ if (global_config_meta || project_config_meta || explicit_file_meta || cmdline_metadata) {
725
+ merged_config = apex_merge_metadata(
726
+ global_config_meta,
727
+ project_config_meta,
728
+ explicit_file_meta,
729
+ cmdline_metadata,
730
+ NULL);
731
+ }
732
+
733
+ if (global_config_meta) apex_free_metadata(global_config_meta);
734
+ if (project_config_meta) apex_free_metadata(project_config_meta);
735
+ if (explicit_file_meta) apex_free_metadata(explicit_file_meta);
736
+ if (global_config_path) free(global_config_path);
737
+ if (project_config_path) free(project_config_path);
738
+
739
+ fprintf(out, "---\n");
740
+ if (merged_config) {
741
+ apex_metadata_fprint_yaml_mapping(out, merged_config);
742
+ }
743
+ fprintf(out, "---\n\n");
744
+
745
+ apex_options eff = *options;
746
+ if (merged_config) {
747
+ apex_apply_metadata_to_options(merged_config, &eff);
748
+ }
749
+ if (plugins_cli_override) {
750
+ eff.enable_plugins = plugins_cli_value;
751
+ }
752
+ apex_free_metadata(merged_config);
753
+
754
+ if (!eff.enable_plugins) {
755
+ fprintf(out, "plugins:\n enabled: false\n");
756
+ return;
757
+ }
758
+
759
+ fprintf(out, "plugins:\n enabled: true\n ids:\n");
760
+
761
+ cli_installed_plugin *installed_head = NULL;
762
+ char **installed_ids = NULL;
763
+ size_t installed_count = 0;
764
+ size_t installed_cap = 0;
765
+
766
+ char cwd[1024];
767
+ cwd[0] = '\0';
768
+ if (getcwd(cwd, sizeof(cwd)) != NULL && cwd[0] != '\0') {
769
+ char cwd_plugins[1200];
770
+ snprintf(cwd_plugins, sizeof(cwd_plugins), "%s/.apex/plugins", cwd);
771
+ cli_collect_installed_from_root(cwd_plugins,
772
+ &installed_head,
773
+ &installed_ids,
774
+ &installed_count,
775
+ &installed_cap);
776
+ }
777
+
778
+ if (options->base_directory && options->base_directory[0] != '\0') {
779
+ char base_plugins[1200];
780
+ snprintf(base_plugins, sizeof(base_plugins), "%s/.apex/plugins", options->base_directory);
781
+ cli_collect_installed_from_root(base_plugins,
782
+ &installed_head,
783
+ &installed_ids,
784
+ &installed_count,
785
+ &installed_cap);
786
+ }
787
+
788
+ char *git_root = apex_cli_git_toplevel();
789
+ if (git_root && git_root[0] != '\0' && cwd[0] != '\0') {
790
+ size_t root_len = strlen(git_root);
791
+ if (strncmp(cwd, git_root, root_len) == 0 &&
792
+ (cwd[root_len] == '/' || cwd[root_len] == '\0')) {
793
+ char git_plugins[1200];
794
+ snprintf(git_plugins, sizeof(git_plugins), "%s/.apex/plugins", git_root);
795
+ cli_collect_installed_from_root(git_plugins,
796
+ &installed_head,
797
+ &installed_ids,
798
+ &installed_count,
799
+ &installed_cap);
800
+ }
801
+ free(git_root);
802
+ }
803
+
804
+ const char *xdg = getenv("XDG_CONFIG_HOME");
805
+ char root[1024];
806
+ root[0] = '\0';
807
+ if (xdg && *xdg) {
808
+ snprintf(root, sizeof(root), "%s/apex/plugins", xdg);
809
+ } else {
810
+ const char *home = getenv("HOME");
811
+ if (home && *home) {
812
+ snprintf(root, sizeof(root), "%s/.config/apex/plugins", home);
813
+ }
814
+ }
815
+ if (root[0] != '\0') {
816
+ cli_collect_installed_from_root(root,
817
+ &installed_head,
818
+ &installed_ids,
819
+ &installed_count,
820
+ &installed_cap);
821
+ }
822
+
823
+ if (installed_ids) {
824
+ for (size_t i = 0; i < installed_count; i++) {
825
+ free(installed_ids[i]);
826
+ }
827
+ free(installed_ids);
828
+ }
829
+
830
+ if (!installed_head) {
831
+ fprintf(out, " # (none installed)\n");
832
+ } else {
833
+ for (cli_installed_plugin *p = installed_head; p; p = p->next) {
834
+ const char *pid = p->id ? p->id : "";
835
+ fprintf(out, " - %s\n", pid);
836
+ }
837
+ }
838
+ cli_free_installed_plugins(installed_head);
839
+ }
840
+
482
841
  /* Profiling helpers (same as in apex.c) */
483
842
  static double get_time_ms(void) {
484
843
  struct timeval tv;
@@ -631,6 +990,9 @@ static void print_usage(const char *program_name) {
631
990
  fprintf(stderr, " --hardbreaks Treat newlines as hard breaks\n");
632
991
  fprintf(stderr, " --header-anchors Generate <a> anchor tags instead of header IDs\n");
633
992
  fprintf(stderr, " -h, --help Show this help message\n");
993
+ fprintf(stderr, " -i, --info Show version, merged config (YAML), and plugin ids; to stdout without files, to stderr when processing files\n");
994
+ fprintf(stderr, " --extract-meta Print merged document metadata from input file(s) as YAML and exit\n");
995
+ fprintf(stderr, " -e, --extract-meta-value KEY Print one metadata value for KEY and exit (uses merged metadata from files, last wins)\n");
634
996
  fprintf(stderr, " --id-format FORMAT Header ID format: gfm (default), mmd, or kramdown\n");
635
997
  fprintf(stderr, " (modes auto-set format; use this to override in unified mode)\n");
636
998
  fprintf(stderr, " --[no-]includes Enable file inclusion (enabled by default in unified mode)\n");
@@ -652,7 +1014,7 @@ static void print_usage(const char *program_name) {
652
1014
  fprintf(stderr, " --mmd-merge Merge files from one or more mmd_merge-style index files into a single Markdown stream\n");
653
1015
  fprintf(stderr, " Index files list document parts line-by-line; indentation controls header level shifting.\n");
654
1016
  fprintf(stderr, " -m, --mode MODE Processor mode: commonmark, gfm, mmd, kramdown, unified (default)\n");
655
- fprintf(stderr, " -t, --to FORMAT Output format: html (default), json (before filters), json-filtered/ast-json/ast (after filters), markdown/md, mmd, commonmark/cmark, kramdown, gfm, terminal/cli, terminal256, man, man-html\n");
1017
+ fprintf(stderr, " -t, --to FORMAT Output format: html (default), xhtml (alias for html + --xhtml), strict-xhtml (alias for html + --strict-xhtml), json (before filters), json-filtered/ast-json/ast (after filters), markdown/md, mmd, commonmark/cmark, kramdown, gfm, terminal/cli, terminal256, man, man-html\n");
656
1018
  fprintf(stderr, " --no-bibliography Suppress bibliography output\n");
657
1019
  fprintf(stderr, " --no-footnotes Disable footnote support\n");
658
1020
  fprintf(stderr, " --no-ids Disable automatic header ID generation\n");
@@ -677,8 +1039,8 @@ static void print_usage(const char *program_name) {
677
1039
  fprintf(stderr, " --[no-]progress Show progress indicator during processing (enabled by default for TTY)\n");
678
1040
  fprintf(stderr, " --plugins Enable external/plugin processing\n");
679
1041
  fprintf(stderr, " --pretty Pretty-print HTML with indentation and whitespace\n");
680
- fprintf(stderr, " --xhtml HTML5 output with self-closing void tags (<br />, <meta ... />)\n");
681
- fprintf(stderr, " --strict-xhtml Polyglot XHTML/XML for parsers (xmlns, application/xhtml+xml meta; implies --xhtml). Mutually exclusive with --xhtml.\n");
1042
+ fprintf(stderr, " --xhtml HTML5 output with self-closing void tags (<br />, <meta ... />). Same as -t xhtml.\n");
1043
+ fprintf(stderr, " --strict-xhtml Polyglot XHTML/XML for parsers (xmlns, application/xhtml+xml meta; implies --xhtml). Mutually exclusive with --xhtml. Same as -t strict-xhtml.\n");
682
1044
  fprintf(stderr, " --reject Reject all Critic Markup changes (revert edits)\n");
683
1045
  fprintf(stderr, " --[no-]relaxed-tables Enable or disable relaxed table parsing (no separator rows required)\n");
684
1046
  fprintf(stderr, " --[no-]per-cell-alignment Enable or disable per-cell alignment markers (colons at start/end of cells, enabled by default in unified mode)\n");
@@ -708,6 +1070,8 @@ static void print_usage(const char *program_name) {
708
1070
  fprintf(stderr, " --[no-]wikilink-sanitize Sanitize wiki link URLs (lowercase, remove apostrophes, etc.)\n");
709
1071
  fprintf(stderr, " --theme NAME Terminal theme name for -t terminal/terminal256 (from ~/.config/apex/terminal/themes/NAME.theme)\n");
710
1072
  fprintf(stderr, " --width N Hard-wrap terminal/terminal256 output at N visible columns\n");
1073
+ fprintf(stderr, " --no-terminal-images Do not render local images via imgcat/chafa/viu/catimg on terminal output\n");
1074
+ fprintf(stderr, " --terminal-image-width N Max width/cells for terminal image tools (default: 50)\n");
711
1075
  fprintf(stderr, " -p, --paginate Page terminal/cli/terminal256 output through a pager (APEX_PAGER, then PAGER, then less -R)\n");
712
1076
  fprintf(stderr, "\n");
713
1077
  fprintf(stderr, "If no file is specified, reads from stdin.\n");
@@ -1522,10 +1886,15 @@ int main(int argc, char *argv[]) {
1522
1886
  init_progress();
1523
1887
 
1524
1888
  apex_options options = apex_options_default();
1889
+ apex_options cli_options_snapshot;
1890
+ apex_cli_option_mask cli_opt_mask = {0};
1525
1891
  bool plugins_cli_override = false;
1526
1892
  bool plugins_cli_value = false;
1527
1893
  bool list_plugins = false;
1528
1894
  bool list_themes = false;
1895
+ bool cli_info = false;
1896
+ bool cli_extract_meta = false;
1897
+ const char *extract_meta_value_key = NULL;
1529
1898
  const char *install_plugin_id = NULL;
1530
1899
  const char *uninstall_plugin_id = NULL;
1531
1900
  bool list_filters = false;
@@ -1585,6 +1954,10 @@ int main(int argc, char *argv[]) {
1585
1954
  /* Pagination for terminal/terminal256 output */
1586
1955
  bool paginate_cli = false;
1587
1956
 
1957
+ /* Terminal inline images: --no-terminal-images / --terminal-image-width N */
1958
+ bool no_terminal_images_cli = false;
1959
+ int terminal_image_width_cli = -1; /* -1 = use options default */
1960
+
1588
1961
  /* Parse command-line arguments */
1589
1962
  for (int i = 1; i < argc; i++) {
1590
1963
  if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
@@ -1593,11 +1966,22 @@ int main(int argc, char *argv[]) {
1593
1966
  } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
1594
1967
  print_version();
1595
1968
  return 0;
1969
+ } else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--info") == 0) {
1970
+ cli_info = true;
1971
+ } else if (strcmp(argv[i], "--extract-meta") == 0) {
1972
+ cli_extract_meta = true;
1973
+ } else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--extract-meta-value") == 0) {
1974
+ if (++i >= argc) {
1975
+ fprintf(stderr, "Error: --extract-meta-value requires a KEY argument\n");
1976
+ return 1;
1977
+ }
1978
+ extract_meta_value_key = argv[i];
1596
1979
  } else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mode") == 0) {
1597
1980
  if (++i >= argc) {
1598
1981
  fprintf(stderr, "Error: --mode requires an argument\n");
1599
1982
  return 1;
1600
1983
  }
1984
+ cli_opt_mask.mode = true;
1601
1985
  if (strcmp(argv[i], "commonmark") == 0) {
1602
1986
  options = apex_options_for_mode(APEX_MODE_COMMONMARK);
1603
1987
  } else if (strcmp(argv[i], "gfm") == 0) {
@@ -1617,8 +2001,23 @@ int main(int argc, char *argv[]) {
1617
2001
  fprintf(stderr, "Error: --to requires an argument\n");
1618
2002
  return 1;
1619
2003
  }
2004
+ cli_opt_mask.output_format = true;
1620
2005
  if (strcmp(argv[i], "html") == 0) {
1621
2006
  options.output_format = APEX_OUTPUT_HTML;
2007
+ } else if (strcmp(argv[i], "xhtml") == 0) {
2008
+ /* Alias for -t html --xhtml */
2009
+ cli_opt_mask.xhtml = true;
2010
+ cli_opt_mask.strict_xhtml = true;
2011
+ options.output_format = APEX_OUTPUT_HTML;
2012
+ options.xhtml = true;
2013
+ options.strict_xhtml = false;
2014
+ } else if (strcmp(argv[i], "strict-xhtml") == 0) {
2015
+ /* Alias for -t html --strict-xhtml */
2016
+ cli_opt_mask.xhtml = true;
2017
+ cli_opt_mask.strict_xhtml = true;
2018
+ options.output_format = APEX_OUTPUT_HTML;
2019
+ options.strict_xhtml = true;
2020
+ options.xhtml = false;
1622
2021
  } else if (strcmp(argv[i], "json") == 0) {
1623
2022
  options.output_format = APEX_OUTPUT_JSON;
1624
2023
  } else if (strcmp(argv[i], "json-filtered") == 0 || strcmp(argv[i], "ast-json") == 0 || strcmp(argv[i], "ast") == 0) {
@@ -1643,7 +2042,7 @@ int main(int argc, char *argv[]) {
1643
2042
  options.output_format = APEX_OUTPUT_MAN_HTML;
1644
2043
  } else {
1645
2044
  fprintf(stderr, "Error: Unknown output format '%s'\n", argv[i]);
1646
- fprintf(stderr, "Supported formats: html, json, json-filtered/ast-json/ast, markdown/md, mmd, commonmark/cmark, kramdown, gfm, terminal/cli, terminal256, man, man-html\n");
2045
+ fprintf(stderr, "Supported formats: html, xhtml, strict-xhtml, json, json-filtered/ast-json/ast, markdown/md, mmd, commonmark/cmark, kramdown, gfm, terminal/cli, terminal256, man, man-html\n");
1647
2046
  return 1;
1648
2047
  }
1649
2048
  } else if (strcmp(argv[i], "--theme") == 0) {
@@ -1651,6 +2050,7 @@ int main(int argc, char *argv[]) {
1651
2050
  fprintf(stderr, "Error: --theme requires a name argument\n");
1652
2051
  return 1;
1653
2052
  }
2053
+ cli_opt_mask.theme_name = true;
1654
2054
  options.theme_name = argv[i];
1655
2055
  } else if (strcmp(argv[i], "--width") == 0) {
1656
2056
  if (++i >= argc) {
@@ -1663,6 +2063,18 @@ int main(int argc, char *argv[]) {
1663
2063
  }
1664
2064
  } else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--paginate") == 0) {
1665
2065
  paginate_cli = true;
2066
+ } else if (strcmp(argv[i], "--no-terminal-images") == 0) {
2067
+ no_terminal_images_cli = true;
2068
+ } else if (strcmp(argv[i], "--terminal-image-width") == 0) {
2069
+ if (++i >= argc) {
2070
+ fprintf(stderr, "Error: --terminal-image-width requires a positive integer\n");
2071
+ return 1;
2072
+ }
2073
+ terminal_image_width_cli = atoi(argv[i]);
2074
+ if (terminal_image_width_cli < 1) {
2075
+ fprintf(stderr, "Error: --terminal-image-width must be at least 1\n");
2076
+ return 1;
2077
+ }
1666
2078
  } else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
1667
2079
  if (++i >= argc) {
1668
2080
  fprintf(stderr, "Error: --output requires an argument\n");
@@ -1670,10 +2082,12 @@ int main(int argc, char *argv[]) {
1670
2082
  }
1671
2083
  output_file = argv[i];
1672
2084
  } else if (strcmp(argv[i], "--plugins") == 0) {
2085
+ cli_opt_mask.enable_plugins = true;
1673
2086
  options.enable_plugins = true;
1674
2087
  plugins_cli_override = true;
1675
2088
  plugins_cli_value = true;
1676
2089
  } else if (strcmp(argv[i], "--no-plugins") == 0) {
2090
+ cli_opt_mask.enable_plugins = true;
1677
2091
  options.enable_plugins = false;
1678
2092
  plugins_cli_override = true;
1679
2093
  plugins_cli_value = false;
@@ -1757,26 +2171,36 @@ int main(int argc, char *argv[]) {
1757
2171
  }
1758
2172
  uninstall_plugin_id = argv[i];
1759
2173
  } else if (strcmp(argv[i], "--no-tables") == 0) {
2174
+ cli_opt_mask.enable_tables = true;
1760
2175
  options.enable_tables = false;
1761
2176
  } else if (strcmp(argv[i], "--no-footnotes") == 0) {
2177
+ cli_opt_mask.enable_footnotes = true;
1762
2178
  options.enable_footnotes = false;
1763
2179
  } else if (strcmp(argv[i], "--no-smart") == 0) {
2180
+ cli_opt_mask.enable_smart_typography = true;
1764
2181
  options.enable_smart_typography = false;
1765
2182
  } else if (strcmp(argv[i], "--no-math") == 0) {
2183
+ cli_opt_mask.enable_math = true;
1766
2184
  options.enable_math = false;
1767
2185
  } else if (strcmp(argv[i], "--includes") == 0) {
2186
+ cli_opt_mask.enable_file_includes = true;
1768
2187
  options.enable_file_includes = true;
1769
2188
  } else if (strcmp(argv[i], "--no-includes") == 0) {
2189
+ cli_opt_mask.enable_file_includes = true;
1770
2190
  options.enable_file_includes = false;
1771
2191
  } else if (strcmp(argv[i], "--hardbreaks") == 0) {
2192
+ cli_opt_mask.hardbreaks = true;
1772
2193
  options.hardbreaks = true;
1773
2194
  } else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--standalone") == 0) {
2195
+ cli_opt_mask.standalone = true;
1774
2196
  options.standalone = true;
1775
2197
  } else if (strcmp(argv[i], "--css") == 0 || strcmp(argv[i], "--style") == 0) {
1776
2198
  if (++i >= argc) {
1777
2199
  fprintf(stderr, "Error: %s requires an argument\n", argv[i-1]);
1778
2200
  return 1;
1779
2201
  }
2202
+ cli_opt_mask.standalone = true;
2203
+ cli_opt_mask.stylesheet = true;
1780
2204
  options.standalone = true; /* Imply standalone if CSS is specified */
1781
2205
 
1782
2206
  /* Parse comma-separated stylesheet paths */
@@ -1841,6 +2265,7 @@ int main(int argc, char *argv[]) {
1841
2265
  }
1842
2266
  }
1843
2267
  } else if (strcmp(argv[i], "--embed-css") == 0) {
2268
+ cli_opt_mask.embed_stylesheet = true;
1844
2269
  options.embed_stylesheet = true;
1845
2270
  } else if (strcmp(argv[i], "--script") == 0) {
1846
2271
  if (++i >= argc) {
@@ -1952,17 +2377,23 @@ int main(int argc, char *argv[]) {
1952
2377
  fprintf(stderr, "Error: --title requires an argument\n");
1953
2378
  return 1;
1954
2379
  }
2380
+ cli_opt_mask.document_title = true;
1955
2381
  options.document_title = argv[i];
1956
2382
  } else if (strcmp(argv[i], "--pretty") == 0) {
2383
+ cli_opt_mask.pretty = true;
1957
2384
  options.pretty = true;
1958
2385
  } else if (strcmp(argv[i], "--xhtml") == 0) {
2386
+ cli_opt_mask.xhtml = true;
1959
2387
  options.xhtml = true;
1960
2388
  } else if (strcmp(argv[i], "--strict-xhtml") == 0) {
2389
+ cli_opt_mask.strict_xhtml = true;
1961
2390
  options.strict_xhtml = true;
1962
2391
  } else if (strcmp(argv[i], "--accept") == 0) {
2392
+ cli_opt_mask.critic = true;
1963
2393
  options.enable_critic_markup = true;
1964
2394
  options.critic_mode = 0; /* CRITIC_ACCEPT */
1965
2395
  } else if (strcmp(argv[i], "--reject") == 0) {
2396
+ cli_opt_mask.critic = true;
1966
2397
  options.enable_critic_markup = true;
1967
2398
  options.critic_mode = 1; /* CRITIC_REJECT */
1968
2399
  } else if (strcmp(argv[i], "--id-format") == 0) {
@@ -1970,6 +2401,7 @@ int main(int argc, char *argv[]) {
1970
2401
  fprintf(stderr, "Error: --id-format requires an argument (gfm, mmd, or kramdown)\n");
1971
2402
  return 1;
1972
2403
  }
2404
+ cli_opt_mask.id_format = true;
1973
2405
  if (strcmp(argv[i], "gfm") == 0) {
1974
2406
  options.id_format = 0; /* GFM format */
1975
2407
  } else if (strcmp(argv[i], "mmd") == 0) {
@@ -1981,22 +2413,29 @@ int main(int argc, char *argv[]) {
1981
2413
  return 1;
1982
2414
  }
1983
2415
  } else if (strcmp(argv[i], "--no-ids") == 0) {
2416
+ cli_opt_mask.generate_header_ids = true;
1984
2417
  options.generate_header_ids = false;
1985
2418
  } else if (strcmp(argv[i], "--header-anchors") == 0) {
2419
+ cli_opt_mask.header_anchors = true;
1986
2420
  options.header_anchors = true;
1987
2421
  } else if (strcmp(argv[i], "--relaxed-tables") == 0) {
2422
+ cli_opt_mask.relaxed_tables = true;
1988
2423
  options.relaxed_tables = true;
1989
2424
  } else if (strcmp(argv[i], "--no-relaxed-tables") == 0) {
2425
+ cli_opt_mask.relaxed_tables = true;
1990
2426
  options.relaxed_tables = false;
1991
2427
  } else if (strcmp(argv[i], "--per-cell-alignment") == 0) {
2428
+ cli_opt_mask.per_cell_alignment = true;
1992
2429
  options.per_cell_alignment = true;
1993
2430
  } else if (strcmp(argv[i], "--no-per-cell-alignment") == 0) {
2431
+ cli_opt_mask.per_cell_alignment = true;
1994
2432
  options.per_cell_alignment = false;
1995
2433
  } else if (strcmp(argv[i], "--captions") == 0) {
1996
2434
  if (++i >= argc) {
1997
2435
  fprintf(stderr, "Error: --captions requires an argument (above or below)\n");
1998
2436
  return 1;
1999
2437
  }
2438
+ cli_opt_mask.caption_position = true;
2000
2439
  if (strcmp(argv[i], "above") == 0) {
2001
2440
  options.caption_position = 0;
2002
2441
  } else if (strcmp(argv[i], "below") == 0) {
@@ -2010,6 +2449,7 @@ int main(int argc, char *argv[]) {
2010
2449
  fprintf(stderr, "Error: --code-highlight requires a tool name (pygments, skylighting, shiki, or abbreviations p, s, sh)\n");
2011
2450
  return 1;
2012
2451
  }
2452
+ cli_opt_mask.code_highlighter = true;
2013
2453
  /* Accept full names and abbreviations */
2014
2454
  if (strcmp(argv[i], "pygments") == 0 || strcmp(argv[i], "p") == 0 || strcmp(argv[i], "pyg") == 0) {
2015
2455
  options.code_highlighter = "pygments";
@@ -2027,95 +2467,132 @@ int main(int argc, char *argv[]) {
2027
2467
  fprintf(stderr, "Error: --code-highlight-theme requires a theme name\n");
2028
2468
  return 1;
2029
2469
  }
2470
+ cli_opt_mask.code_highlight_theme = true;
2030
2471
  options.code_highlight_theme = argv[i];
2031
2472
  } else if (strcmp(argv[i], "--code-line-numbers") == 0) {
2473
+ cli_opt_mask.code_line_numbers = true;
2032
2474
  options.code_line_numbers = true;
2033
2475
  } else if (strcmp(argv[i], "--highlight-language-only") == 0) {
2476
+ cli_opt_mask.highlight_language_only = true;
2034
2477
  options.highlight_language_only = true;
2035
2478
  } else if (strcmp(argv[i], "--alpha-lists") == 0) {
2479
+ cli_opt_mask.allow_alpha_lists = true;
2036
2480
  options.allow_alpha_lists = true;
2037
2481
  } else if (strcmp(argv[i], "--no-alpha-lists") == 0) {
2482
+ cli_opt_mask.allow_alpha_lists = true;
2038
2483
  options.allow_alpha_lists = false;
2039
2484
  } else if (strcmp(argv[i], "--mixed-lists") == 0) {
2485
+ cli_opt_mask.allow_mixed_list_markers = true;
2040
2486
  options.allow_mixed_list_markers = true;
2041
2487
  } else if (strcmp(argv[i], "--no-mixed-lists") == 0) {
2488
+ cli_opt_mask.allow_mixed_list_markers = true;
2042
2489
  options.allow_mixed_list_markers = false;
2043
2490
  } else if (strcmp(argv[i], "--unsafe") == 0) {
2491
+ cli_opt_mask.unsafe = true;
2044
2492
  options.unsafe = true;
2045
2493
  } else if (strcmp(argv[i], "--no-unsafe") == 0) {
2494
+ cli_opt_mask.unsafe = true;
2046
2495
  options.unsafe = false;
2047
2496
  } else if (strcmp(argv[i], "--sup-sub") == 0) {
2497
+ cli_opt_mask.enable_sup_sub = true;
2048
2498
  options.enable_sup_sub = true;
2049
2499
  } else if (strcmp(argv[i], "--no-sup-sub") == 0) {
2500
+ cli_opt_mask.enable_sup_sub = true;
2050
2501
  options.enable_sup_sub = false;
2051
2502
  } else if (strcmp(argv[i], "--divs") == 0) {
2503
+ cli_opt_mask.enable_divs = true;
2052
2504
  options.enable_divs = true;
2053
2505
  } else if (strcmp(argv[i], "--no-divs") == 0) {
2506
+ cli_opt_mask.enable_divs = true;
2054
2507
  options.enable_divs = false;
2055
2508
  } else if (strcmp(argv[i], "--one-line-definitions") == 0) {
2509
+ cli_opt_mask.enable_definition_lists = true;
2056
2510
  options.enable_definition_lists = true;
2057
2511
  } else if (strcmp(argv[i], "--no-one-line-definitions") == 0) {
2512
+ cli_opt_mask.enable_definition_lists = true;
2058
2513
  options.enable_definition_lists = false;
2059
2514
  } else if (strcmp(argv[i], "--spans") == 0) {
2515
+ cli_opt_mask.enable_spans = true;
2060
2516
  options.enable_spans = true;
2061
2517
  } else if (strcmp(argv[i], "--no-spans") == 0) {
2518
+ cli_opt_mask.enable_spans = true;
2062
2519
  options.enable_spans = false;
2063
2520
  } else if (strcmp(argv[i], "--autolink") == 0) {
2521
+ cli_opt_mask.enable_autolink = true;
2064
2522
  options.enable_autolink = true;
2065
2523
  } else if (strcmp(argv[i], "--no-autolink") == 0) {
2524
+ cli_opt_mask.enable_autolink = true;
2066
2525
  options.enable_autolink = false;
2067
2526
  } else if (strcmp(argv[i], "--strikethrough") == 0) {
2527
+ cli_opt_mask.enable_strikethrough = true;
2068
2528
  options.enable_strikethrough = true;
2069
2529
  } else if (strcmp(argv[i], "--no-strikethrough") == 0) {
2530
+ cli_opt_mask.enable_strikethrough = true;
2070
2531
  options.enable_strikethrough = false;
2071
2532
  } else if (strcmp(argv[i], "--obfuscate-emails") == 0) {
2533
+ cli_opt_mask.obfuscate_emails = true;
2072
2534
  options.obfuscate_emails = true;
2073
2535
  } else if (strcmp(argv[i], "--progress") == 0) {
2074
2536
  progress_enabled = true;
2075
2537
  } else if (strcmp(argv[i], "--no-progress") == 0) {
2076
2538
  progress_enabled = false;
2077
2539
  } else if (strcmp(argv[i], "--aria") == 0) {
2540
+ cli_opt_mask.enable_aria = true;
2078
2541
  options.enable_aria = true;
2079
- } else if (strcmp(argv[i], "--no-plugins") == 0) {
2080
- options.enable_plugins = false;
2081
2542
  } else if (strcmp(argv[i], "--wikilinks") == 0) {
2543
+ cli_opt_mask.enable_wiki_links = true;
2082
2544
  options.enable_wiki_links = true;
2083
2545
  } else if (strcmp(argv[i], "--no-wikilinks") == 0) {
2546
+ cli_opt_mask.enable_wiki_links = true;
2084
2547
  options.enable_wiki_links = false;
2085
2548
  } else if (strcmp(argv[i], "--emoji-autocorrect") == 0) {
2549
+ cli_opt_mask.enable_emoji_autocorrect = true;
2086
2550
  options.enable_emoji_autocorrect = true;
2087
2551
  } else if (strcmp(argv[i], "--no-emoji-autocorrect") == 0) {
2552
+ cli_opt_mask.enable_emoji_autocorrect = true;
2088
2553
  options.enable_emoji_autocorrect = false;
2089
2554
  } else if (strcmp(argv[i], "--widont") == 0) {
2555
+ cli_opt_mask.enable_widont = true;
2090
2556
  options.enable_widont = true;
2091
2557
  } else if (strcmp(argv[i], "--code-is-poetry") == 0) {
2558
+ cli_opt_mask.code_is_poetry = true;
2092
2559
  options.code_is_poetry = true;
2093
2560
  options.highlight_language_only = true;
2094
2561
  } else if (strcmp(argv[i], "--markdown-in-html") == 0) {
2562
+ cli_opt_mask.enable_markdown_in_html = true;
2095
2563
  options.enable_markdown_in_html = true;
2096
2564
  } else if (strcmp(argv[i], "--no-markdown-in-html") == 0) {
2565
+ cli_opt_mask.enable_markdown_in_html = true;
2097
2566
  options.enable_markdown_in_html = false;
2098
2567
  } else if (strcmp(argv[i], "--random-footnote-ids") == 0) {
2568
+ cli_opt_mask.random_footnote_ids = true;
2099
2569
  options.random_footnote_ids = true;
2100
2570
  } else if (strcmp(argv[i], "--hashtags") == 0) {
2571
+ cli_opt_mask.enable_hashtags = true;
2101
2572
  options.enable_hashtags = true;
2102
2573
  } else if (strcmp(argv[i], "--style-hashtags") == 0) {
2574
+ cli_opt_mask.style_hashtags = true;
2103
2575
  options.style_hashtags = true;
2104
2576
  } else if (strcmp(argv[i], "--proofreader") == 0) {
2577
+ cli_opt_mask.proofreader = true;
2105
2578
  options.proofreader_mode = true;
2106
2579
  options.enable_critic_markup = true;
2107
2580
  options.critic_mode = 2; /* Ensure markup mode */
2108
2581
  } else if (strcmp(argv[i], "--hr-page-break") == 0) {
2582
+ cli_opt_mask.hr_page_break = true;
2109
2583
  options.hr_page_break = true;
2110
2584
  } else if (strcmp(argv[i], "--title-from-h1") == 0) {
2585
+ cli_opt_mask.title_from_h1 = true;
2111
2586
  options.title_from_h1 = true;
2112
2587
  } else if (strcmp(argv[i], "--page-break-before-footnotes") == 0) {
2588
+ cli_opt_mask.page_break_before_footnotes = true;
2113
2589
  options.page_break_before_footnotes = true;
2114
2590
  } else if (strcmp(argv[i], "--wikilink-space") == 0) {
2115
2591
  if (++i >= argc) {
2116
2592
  fprintf(stderr, "Error: --wikilink-space requires an argument (dash, none, underscore, or space)\n");
2117
2593
  return 1;
2118
2594
  }
2595
+ cli_opt_mask.wikilink_space = true;
2119
2596
  if (strcmp(argv[i], "dash") == 0) {
2120
2597
  options.wikilink_space = 0;
2121
2598
  } else if (strcmp(argv[i], "none") == 0) {
@@ -2133,31 +2610,42 @@ int main(int argc, char *argv[]) {
2133
2610
  fprintf(stderr, "Error: --wikilink-extension requires an argument\n");
2134
2611
  return 1;
2135
2612
  }
2613
+ cli_opt_mask.wikilink_extension = true;
2136
2614
  options.wikilink_extension = argv[i];
2137
2615
  } else if (strcmp(argv[i], "--wikilink-sanitize") == 0) {
2616
+ cli_opt_mask.wikilink_sanitize = true;
2138
2617
  options.wikilink_sanitize = true;
2139
2618
  } else if (strcmp(argv[i], "--no-wikilink-sanitize") == 0) {
2619
+ cli_opt_mask.wikilink_sanitize = true;
2140
2620
  options.wikilink_sanitize = false;
2141
2621
  } else if (strcmp(argv[i], "--transforms") == 0) {
2622
+ cli_opt_mask.enable_metadata_transforms = true;
2142
2623
  options.enable_metadata_transforms = true;
2143
2624
  } else if (strcmp(argv[i], "--no-transforms") == 0) {
2625
+ cli_opt_mask.enable_metadata_transforms = true;
2144
2626
  options.enable_metadata_transforms = false;
2145
2627
  } else if (strcmp(argv[i], "--embed-images") == 0) {
2628
+ cli_opt_mask.embed_images = true;
2146
2629
  options.embed_images = true;
2147
2630
  } else if (strcmp(argv[i], "--image-captions") == 0) {
2631
+ cli_opt_mask.enable_image_captions = true;
2148
2632
  options.enable_image_captions = true;
2149
2633
  } else if (strcmp(argv[i], "--no-image-captions") == 0) {
2634
+ cli_opt_mask.enable_image_captions = true;
2150
2635
  options.enable_image_captions = false;
2151
2636
  } else if (strcmp(argv[i], "--title-captions-only") == 0) {
2637
+ cli_opt_mask.title_captions_only = true;
2152
2638
  options.title_captions_only = true;
2153
2639
  options.enable_image_captions = true; /* implied when title-captions-only is set */
2154
2640
  } else if (strcmp(argv[i], "--no-title-captions-only") == 0) {
2641
+ cli_opt_mask.title_captions_only = true;
2155
2642
  options.title_captions_only = false;
2156
2643
  } else if (strcmp(argv[i], "--base-dir") == 0) {
2157
2644
  if (++i >= argc) {
2158
2645
  fprintf(stderr, "Error: --base-dir requires an argument\n");
2159
2646
  return 1;
2160
2647
  }
2648
+ cli_opt_mask.base_directory = true;
2161
2649
  options.base_directory = argv[i];
2162
2650
  } else if (strcmp(argv[i], "--bibliography") == 0) {
2163
2651
  if (++i >= argc) {
@@ -2180,6 +2668,7 @@ int main(int argc, char *argv[]) {
2180
2668
  }
2181
2669
  bibliography_files = new_files;
2182
2670
  }
2671
+ cli_opt_mask.bibliography = true;
2183
2672
  bibliography_files[bibliography_count++] = argv[i];
2184
2673
  options.enable_citations = true; /* Enable citations when bibliography is provided */
2185
2674
  } else if (strcmp(argv[i], "--csl") == 0) {
@@ -2187,22 +2676,29 @@ int main(int argc, char *argv[]) {
2187
2676
  fprintf(stderr, "Error: --csl requires an argument\n");
2188
2677
  return 1;
2189
2678
  }
2679
+ cli_opt_mask.csl_file = true;
2190
2680
  options.csl_file = argv[i];
2191
2681
  options.enable_citations = true; /* Enable citations when CSL is provided */
2192
2682
  } else if (strcmp(argv[i], "--no-bibliography") == 0) {
2683
+ cli_opt_mask.suppress_bibliography = true;
2193
2684
  options.suppress_bibliography = true;
2194
2685
  } else if (strcmp(argv[i], "--link-citations") == 0) {
2686
+ cli_opt_mask.link_citations = true;
2195
2687
  options.link_citations = true;
2196
2688
  } else if (strcmp(argv[i], "--show-tooltips") == 0) {
2689
+ cli_opt_mask.show_tooltips = true;
2197
2690
  options.show_tooltips = true;
2198
2691
  } else if (strcmp(argv[i], "--indices") == 0) {
2692
+ cli_opt_mask.indices = true;
2199
2693
  options.enable_indices = true;
2200
2694
  options.enable_mmark_index_syntax = true;
2201
2695
  options.enable_textindex_syntax = true;
2202
2696
  options.enable_leanpub_index_syntax = true;
2203
2697
  } else if (strcmp(argv[i], "--no-indices") == 0) {
2698
+ cli_opt_mask.indices = true;
2204
2699
  options.enable_indices = false;
2205
2700
  } else if (strcmp(argv[i], "--no-index") == 0) {
2701
+ cli_opt_mask.suppress_index = true;
2206
2702
  options.suppress_index = true;
2207
2703
  } else if (strcmp(argv[i], "--meta-file") == 0) {
2208
2704
  if (++i >= argc) {
@@ -2293,12 +2789,74 @@ int main(int argc, char *argv[]) {
2293
2789
  return 1;
2294
2790
  }
2295
2791
 
2792
+ if (cli_info && (cli_extract_meta || extract_meta_value_key)) {
2793
+ fprintf(stderr, "Error: --info cannot be combined with --extract-meta or --extract-meta-value\n");
2794
+ return 1;
2795
+ }
2796
+ if (cli_extract_meta && extract_meta_value_key) {
2797
+ fprintf(stderr, "Error: --extract-meta cannot be combined with --extract-meta-value\n");
2798
+ return 1;
2799
+ }
2800
+
2296
2801
  /* Handle theme listing before normal conversion */
2297
2802
  if (list_themes) {
2298
2803
  apex_cli_print_highlight_themes();
2299
2804
  return 0;
2300
2805
  }
2301
2806
 
2807
+ /* Extract document metadata and exit (before plugin/network subcommands) */
2808
+ if (cli_extract_meta || extract_meta_value_key) {
2809
+ if (mmd_merge_mode) {
2810
+ fprintf(stderr, "Error: --extract-meta/--extract-meta-value cannot be used with --mmd-merge\n");
2811
+ return 1;
2812
+ }
2813
+ bool has_files = (input_file != NULL) || (combine_mode && combine_file_count > 0);
2814
+ if (!has_files) {
2815
+ fprintf(stderr, "Error: --extract-meta requires at least one input file\n");
2816
+ return 1;
2817
+ }
2818
+
2819
+ apex_metadata_item *docmeta = NULL;
2820
+ int merge_rc;
2821
+ if (combine_mode) {
2822
+ merge_rc = apex_cli_merge_doc_metadata_from_files(options.mode, combine_files, combine_file_count, &docmeta);
2823
+ } else {
2824
+ char *one_path[1];
2825
+ one_path[0] = (char *)input_file;
2826
+ merge_rc = apex_cli_merge_doc_metadata_from_files(options.mode, one_path, 1, &docmeta);
2827
+ }
2828
+ if (merge_rc != 0) {
2829
+ if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
2830
+ return 1;
2831
+ }
2832
+
2833
+ if (cli_extract_meta) {
2834
+ apex_metadata_fprint_yaml_document(stdout, docmeta);
2835
+ if (docmeta) apex_free_metadata(docmeta);
2836
+ if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
2837
+ return 0;
2838
+ }
2839
+
2840
+ const char *val = apex_metadata_get(docmeta, extract_meta_value_key);
2841
+ if (!val) {
2842
+ fprintf(stderr, "Error: metadata key '%s' not found\n", extract_meta_value_key);
2843
+ if (docmeta) apex_free_metadata(docmeta);
2844
+ if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
2845
+ return 1;
2846
+ }
2847
+ printf("%s\n", val);
2848
+ if (docmeta) apex_free_metadata(docmeta);
2849
+ if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
2850
+ return 0;
2851
+ }
2852
+
2853
+ /* --info with no input files: print and exit */
2854
+ if (cli_info && !input_file && !combine_mode && !mmd_merge_mode) {
2855
+ apex_cli_print_info(stdout, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
2856
+ if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
2857
+ return 0;
2858
+ }
2859
+
2302
2860
  /* Handle plugin listing/installation/uninstallation commands before normal conversion */
2303
2861
  if (list_plugins || install_plugin_id || uninstall_plugin_id) {
2304
2862
  if ((install_plugin_id && uninstall_plugin_id) || (install_plugin_id && list_plugins && uninstall_plugin_id)) {
@@ -3094,6 +3652,10 @@ int main(int argc, char *argv[]) {
3094
3652
  return 1;
3095
3653
  }
3096
3654
 
3655
+ if (cli_info) {
3656
+ apex_cli_print_info(stderr, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
3657
+ }
3658
+
3097
3659
  int rc = 0;
3098
3660
  for (size_t i = 0; i < mmd_merge_file_count; i++) {
3099
3661
  const char *path = mmd_merge_files[i];
@@ -3121,6 +3683,10 @@ int main(int argc, char *argv[]) {
3121
3683
  }
3122
3684
  }
3123
3685
 
3686
+ if (cli_info) {
3687
+ apex_cli_print_info(stderr, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
3688
+ }
3689
+
3124
3690
  int rc = 0;
3125
3691
  bool needs_separator = false;
3126
3692
 
@@ -3177,6 +3743,10 @@ int main(int argc, char *argv[]) {
3177
3743
  }
3178
3744
  }
3179
3745
 
3746
+ if (cli_info && input_file && !combine_mode && !mmd_merge_mode) {
3747
+ apex_cli_print_info(stderr, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
3748
+ }
3749
+
3180
3750
  /* Set input_file_path for plugins (APEX_FILE_PATH) */
3181
3751
  if (input_file) {
3182
3752
  /* When a file is provided, use the original path (as passed in) */
@@ -3310,130 +3880,80 @@ int main(int argc, char *argv[]) {
3310
3880
  size_t enhanced_len = input_len;
3311
3881
 
3312
3882
  if (merged_metadata) {
3313
- /* Build YAML front matter from merged metadata */
3314
- size_t yaml_size = 512;
3315
- char *yaml_buf = malloc(yaml_size);
3316
- if (yaml_buf) {
3317
- size_t yaml_pos = 0;
3318
- yaml_buf[yaml_pos++] = '-';
3319
- yaml_buf[yaml_pos++] = '-';
3320
- yaml_buf[yaml_pos++] = '-';
3321
- yaml_buf[yaml_pos++] = '\n';
3322
-
3323
- /* Use extracted metadata position if available */
3324
- bool has_existing_metadata = (doc_metadata_end > 0);
3325
- size_t metadata_start_pos = 0;
3326
- size_t metadata_end_pos = doc_metadata_end;
3327
-
3328
- /* Add all merged metadata */
3329
- apex_metadata_item *item = merged_metadata;
3330
- while (item) {
3331
- /* Escape value if it contains special characters */
3332
- bool needs_quotes = strchr(item->value, ':') || strchr(item->value, '\n') ||
3333
- strchr(item->value, '"') || strchr(item->value, '\\');
3334
-
3335
- size_t needed = strlen(item->key) + strlen(item->value) + (needs_quotes ? 4 : 0) + 10;
3336
- if (yaml_pos + needed >= yaml_size) {
3337
- yaml_size = (yaml_pos + needed) * 2;
3338
- char *new_buf = realloc(yaml_buf, yaml_size);
3339
- if (!new_buf) {
3340
- free(yaml_buf);
3341
- yaml_buf = NULL;
3342
- break;
3343
- }
3344
- yaml_buf = new_buf;
3345
- }
3883
+ bool has_existing_metadata = (doc_metadata_end > 0);
3884
+ size_t metadata_start_pos = 0;
3885
+ size_t metadata_end_pos = doc_metadata_end;
3886
+
3887
+ char *yaml_buf = NULL;
3888
+ size_t yaml_sz = 0;
3889
+ FILE *ym = open_memstream(&yaml_buf, &yaml_sz);
3890
+ if (ym) {
3891
+ fprintf(ym, "---\n");
3892
+ apex_metadata_fprint_yaml_mapping(ym, merged_metadata);
3893
+ fprintf(ym, "---\n");
3894
+ fclose(ym);
3895
+ }
3346
3896
 
3347
- if (needs_quotes) {
3348
- int written = snprintf(yaml_buf + yaml_pos, yaml_size - yaml_pos, "%s: \"%s\"\n", item->key, item->value);
3349
- if (written > 0) yaml_pos += written;
3350
- } else {
3351
- int written = snprintf(yaml_buf + yaml_pos, yaml_size - yaml_pos, "%s: %s\n", item->key, item->value);
3352
- if (written > 0) yaml_pos += written;
3353
- }
3354
- item = item->next;
3355
- }
3356
-
3357
- if (yaml_buf) {
3358
- /* Add closing --- */
3359
- yaml_buf[yaml_pos++] = '-';
3360
- yaml_buf[yaml_pos++] = '-';
3361
- yaml_buf[yaml_pos++] = '-';
3362
- yaml_buf[yaml_pos++] = '\n';
3363
- yaml_buf[yaml_pos] = '\0';
3364
-
3365
- if (has_existing_metadata) {
3366
- /* Replace existing metadata */
3367
- size_t before_len = metadata_start_pos;
3368
- size_t after_len = input_len - metadata_end_pos;
3369
- enhanced_len = before_len + yaml_pos + after_len;
3370
- enhanced_markdown = malloc(enhanced_len + 1);
3371
- if (enhanced_markdown) {
3372
- if (before_len > 0) {
3373
- memcpy(enhanced_markdown, markdown, before_len);
3374
- }
3375
- memcpy(enhanced_markdown + before_len, yaml_buf, yaml_pos);
3376
- if (after_len > 0) {
3377
- memcpy(enhanced_markdown + before_len + yaml_pos, markdown + metadata_end_pos, after_len);
3378
- }
3379
- enhanced_markdown[enhanced_len] = '\0';
3897
+ if (yaml_buf && yaml_sz > 0) {
3898
+ size_t yaml_pos = yaml_sz;
3899
+
3900
+ if (has_existing_metadata) {
3901
+ /* Replace existing metadata */
3902
+ size_t before_len = metadata_start_pos;
3903
+ size_t after_len = input_len - metadata_end_pos;
3904
+ enhanced_len = before_len + yaml_pos + after_len;
3905
+ enhanced_markdown = malloc(enhanced_len + 1);
3906
+ if (enhanced_markdown) {
3907
+ if (before_len > 0) {
3908
+ memcpy(enhanced_markdown, markdown, before_len);
3380
3909
  }
3381
- } else {
3382
- /* Prepend metadata */
3383
- enhanced_len = yaml_pos + input_len;
3384
- enhanced_markdown = malloc(enhanced_len + 1);
3385
- if (enhanced_markdown) {
3386
- memcpy(enhanced_markdown, yaml_buf, yaml_pos);
3387
- memcpy(enhanced_markdown + yaml_pos, markdown, input_len);
3388
- enhanced_markdown[enhanced_len] = '\0';
3910
+ memcpy(enhanced_markdown + before_len, yaml_buf, yaml_pos);
3911
+ if (after_len > 0) {
3912
+ memcpy(enhanced_markdown + before_len + yaml_pos, markdown + metadata_end_pos, after_len);
3389
3913
  }
3914
+ enhanced_markdown[enhanced_len] = '\0';
3915
+ }
3916
+ } else {
3917
+ /* Prepend metadata */
3918
+ enhanced_len = yaml_pos + input_len;
3919
+ enhanced_markdown = malloc(enhanced_len + 1);
3920
+ if (enhanced_markdown) {
3921
+ memcpy(enhanced_markdown, yaml_buf, yaml_pos);
3922
+ memcpy(enhanced_markdown + yaml_pos, markdown, input_len);
3923
+ enhanced_markdown[enhanced_len] = '\0';
3390
3924
  }
3391
- free(yaml_buf);
3392
3925
  }
3926
+ free(yaml_buf);
3393
3927
  }
3394
3928
  }
3395
3929
  PROFILE_END(metadata_yaml_build);
3396
3930
 
3397
3931
  /* Set bibliography files in options (NULL-terminated array) */
3398
- char **saved_bibliography_files = NULL;
3399
3932
  if (bibliography_count > 0) {
3400
3933
  bibliography_files = realloc(bibliography_files, (bibliography_count + 1) * sizeof(char*));
3401
3934
  if (bibliography_files) {
3402
3935
  bibliography_files[bibliography_count] = NULL; /* NULL terminator */
3403
3936
  options.bibliography_files = bibliography_files;
3404
- /* Save reference in case metadata mode resets options */
3405
- saved_bibliography_files = bibliography_files;
3406
3937
  }
3407
3938
  }
3408
3939
 
3409
3940
  /* Set stylesheet files in options (NULL-terminated array) */
3410
- char **saved_stylesheet_files = NULL;
3411
3941
  if (stylesheet_count > 0) {
3412
3942
  stylesheet_files = realloc(stylesheet_files, (stylesheet_count + 1) * sizeof(char*));
3413
3943
  if (stylesheet_files) {
3414
3944
  stylesheet_files[stylesheet_count] = NULL; /* NULL terminator */
3415
3945
  options.stylesheet_paths = (const char **)stylesheet_files;
3416
3946
  options.stylesheet_count = stylesheet_count;
3417
- /* Save reference in case metadata mode resets options */
3418
- saved_stylesheet_files = stylesheet_files;
3419
3947
  }
3420
3948
  }
3421
3949
 
3422
3950
  /* Apply metadata to options - allows per-document control of command-line options */
3423
3951
  /* Note: Bibliography file loading from metadata will be handled in citations extension */
3424
- apex_output_format_t saved_output_format = options.output_format;
3425
3952
  if (merged_metadata) {
3953
+ /* Snapshot argv-resolved options after wiring bibliography/stylesheet; merged metadata must not override explicit CLI flags. */
3954
+ cli_options_snapshot = options;
3426
3955
  apex_apply_metadata_to_options(merged_metadata, &options);
3427
- /* Restore explicit CLI choices that metadata mode reset */
3428
- options.output_format = saved_output_format;
3429
- if (saved_bibliography_files && !options.bibliography_files) {
3430
- options.bibliography_files = saved_bibliography_files;
3431
- }
3432
- /* Restore stylesheet files if they were lost (e.g., if mode was set in metadata) */
3433
- if (saved_stylesheet_files && !options.stylesheet_paths) {
3434
- options.stylesheet_paths = (const char **)saved_stylesheet_files;
3435
- options.stylesheet_count = stylesheet_count;
3436
- }
3956
+ apex_cli_restore_argv_options(&options, &cli_options_snapshot, &cli_opt_mask);
3437
3957
  }
3438
3958
 
3439
3959
  /* Re-apply explicit CLI override for plugins so it wins over metadata. */
@@ -3698,6 +4218,13 @@ int main(int argc, char *argv[]) {
3698
4218
  options.enable_smart_typography = false;
3699
4219
  }
3700
4220
 
4221
+ if (no_terminal_images_cli) {
4222
+ options.terminal_inline_images = false;
4223
+ }
4224
+ if (terminal_image_width_cli > 0) {
4225
+ options.terminal_image_width = terminal_image_width_cli;
4226
+ }
4227
+
3701
4228
  /* Convert to output (HTML, Markdown, terminal, etc.) */
3702
4229
  char *html = apex_markdown_to_html(final_markdown, final_len, &options);
3703
4230