NoTeX 1.0.0
A modern noteworthy LaTeX template
Loading...
Searching...
No Matches
orchestrator.cpp
1
9
10#include "orchestrator.hpp"
11
12#include <algorithm>
13#include <filesystem>
14#include <iostream>
15#include <string>
16#include <vector>
17
18#include "notex/environment.hpp"
19#include "notex/installer.hpp"
20#include "notex/logging.hpp"
21#include "notex/manager.hpp"
22#include "notex/output.hpp"
23#include "notex/reference.hpp"
24#include "notex/system.hpp"
25#include "notex/templates.hpp"
26
27namespace notex {
28
29Orchestrator::Orchestrator() { register_commands(); }
30
31void Orchestrator::run_version() const { ui::info(PROJECT_VERSION); }
32
33void Orchestrator::run_help() const {
34 // app_.help() itself won't do here: by the time this callback runs,
35 // "help" has already been recorded as app_'s selected subcommand, and
36 // App::help() delegates to a selected subcommand's own (much
37 // shorter) help text rather than the top-level one --help shows.
38 // Calling the formatter directly, exactly as App::exit()'s
39 // CallForHelp branch does, bypasses that delegation.
40 std::cout << app_.get_formatter()->make_help(
41 &app_, app_.get_name(), CLI::AppFormatMode::Normal);
42}
43
44void Orchestrator::run_info() const {
45 // OLD:
46 // ui::step("NoTeX");
47 // ui::info(std::string("Version: ") + PROJECT_VERSION);
48 // ui::info(std::string("Author: ") + PROJECT_AUTHOR);
49 // ui::info(std::string("License: ") + PROJECT_LICENSE);
50 // ui::info(std::string("Homepage: ") + PROJECT_HOMEPAGE_URL);
51 // ui::info(std::string("Description: ") + PROJECT_DESCRIPTION);
52
53 // NEW:
55 std::string("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
56 "━━━━━━━━━━━━━━━━━━━┓"));
58 std::string("┃ "
59 " ┃"));
61 std::string("┃ ░███ ░███ "
62 " ┃"));
64 std::string("┃ ░██ ░███ ░██ ░██ "
65 " ┃"));
67 std::string("┃ ░██ ░████ ░██ ░██ "
68 " ┃"));
70 std::string("┃ ░██ ░██░██ ░██ ░██ "
71 " ┃"));
73 std::string("┃ ░███ ░██ ░██ ░██ ░██ "
74 " ┃"));
76 std::string("┃ ░██ ░██ ░██░██ ░██ "
77 " ┃"));
79 std::string("┃ ░██ ░██ ░████ ░██ "
80 " ┃"));
82 std::string("┃ ░██ ░██ ░███ ░██ "
83 " ┃"));
85 std::string("┃ ░███ ░███ "
86 " ┃"));
88 std::string("┃ "
89 " ┃"));
91 std::string("┃ ~ N O T E X ~ "
92 " ┃"));
93 ui::info(std::string("┃ ") + PROJECT_DESCRIPTION +
94 std::string(" ┃"));
95 ui::info(std::string("┃ Version ") +
96 PROJECT_VERSION +
97 std::string(" ┃"));
99 std::string("┃ "
100 " ┃"));
101 ui::info(std::string("┃ Copyright (c) 2026 ") +
102 PROJECT_AUTHOR + std::string(" ┃"));
103 ui::info(std::string("┃ ") +
104 PROJECT_LICENSE +
105 std::string(" License ┃"));
106 ui::info(
107 std::string("┃ "
108 " ┃"));
109 ui::info(std::string("┃ ") + PROJECT_HOMEPAGE_URL +
110 std::string(" ┃"));
111 ui::info(
112 std::string("┃ "
113 " ┃"));
114 ui::info(
115 std::string("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
116 "━━━━━━━━━━━━━━━━━━━┛"));
117
118 ui::step("Environment");
119 try {
120 const Environment environment;
121 ui::info(std::string(" TEXMFHOME: ") +
122 environment.texmf_home().string());
123 ui::info(std::string(" Global class dir: ") +
124 environment.global_latex_dir().string());
125 ui::info(std::string(" Local class dir: ") +
126 environment.local_latex_dir().string());
127 ui::info(std::string(" Installation: ") +
128 std::string(to_string(environment.installation_type())));
129 } catch (const EnvironmentError& e) {
130 PLOG_WARNING << e.what();
131 ui::warning(e.what());
132 }
133
134 ui::step("Project");
135 try {
136 const Manager manager;
137 const ProjectConfig& config = manager.config();
138 ui::info(std::string(" Root: ") +
139 manager.root_dir().string());
140 ui::info(
141 std::string(" Project type: ") +
142 (config.project_type.empty() ? "(unknown)" : config.project_type));
143 ui::info(std::string(" Main file: ") + config.main_file);
144 ui::info(std::string(" Installation type: ") +
145 (config.installation_type.empty() ? "(unknown)"
146 : config.installation_type));
147 ui::info(std::string(" Theme: ") +
148 (config.theme.empty() ? "(none)" : config.theme));
149 } catch (const ProjectNotFoundError&) {
150 ui::info(" Not inside a NoTeX project (.notex/ directory not found).");
151 }
152}
153
154void Orchestrator::run_clean() const {
155 const std::filesystem::path path(clean_path_);
156
157 if (!Manager::find_project_root(path).has_value()) {
158 PLOG_WARNING << "'clean' invoked outside a NoTeX project; cleaning '"
159 << path.string() << "' directly.";
160 ui::warning("You're not inside a NoTeX project.");
161 ui::info("Cleaning inside '" + path.string() + "' directly.");
162 }
163
164 const CleanReport report = Manager::clean(path, clean_dry_run_);
165
166 const std::string verb = clean_dry_run_ ? "Would remove: " : "Removed: ";
167 for (const auto& file : report.removed_files) {
168 ui::step(verb + file.string());
169 }
170 for (const auto& directory : report.removed_directories) {
171 ui::step(verb + directory.string());
172 }
173
174 if (report.total_removed() == 0) {
175 PLOG_INFO << "Clean found nothing to remove in '" << path.string()
176 << "'.";
177 ui::success("Nothing to clean.");
178 } else {
179 const std::string count = std::to_string(report.total_removed());
180 PLOG_INFO << count << " item(s) "
181 << (clean_dry_run_ ? "would be removed from '"
182 : "removed from '")
183 << path.string() << "'.";
184 ui::success(count + " item(s) " +
185 (clean_dry_run_ ? "would be removed." : "removed."));
186 }
187}
188
189void Orchestrator::run_install_global() const {
190 const Environment environment;
191 if (Installer::install_global(environment, install_force_)) {
192 PLOG_INFO << "Installed NoTeX globally into '"
193 << environment.global_latex_dir().string() << "'.";
194 ui::success("Installed NoTeX globally into '" +
195 environment.global_latex_dir().string() + "'.");
196 }
197}
198
199void Orchestrator::run_install_local() const {
200 const std::filesystem::path target_dir(install_path_);
201 if (Installer::install_local(target_dir, install_force_)) {
202 PLOG_INFO << "Installed NoTeX locally into '"
203 << (target_dir / "settings").string() << "'.";
205 "Installed NoTeX locally into '" +
206 (target_dir / "settings").string() +
207 "'. Documents should refer to \\documentclass{settings/notex}.");
208 }
209}
210
211void Orchestrator::run_init() const {
212 // `notex init .` and `notex init multi .` are equivalent (DESIGN.md,
213 // "initialization"): the first positional is only a type when it
214 // parses as one and a second positional was also given; a lone
215 // positional is always the path, defaulting the type to multi-file.
217 std::string path = ".";
218
219 if (!init_path_arg_.empty()) {
220 const auto parsed = templates::project_type_from_string(init_type_arg_);
221 if (!parsed.has_value()) {
222 throw UsageError("'" + init_type_arg_ +
223 "' is not a valid project type; expected "
224 "'mono' or 'multi'.");
225 }
226 project_type = *parsed;
227 path = init_path_arg_;
228 } else if (!init_type_arg_.empty()) {
229 if (const auto parsed =
231 parsed.has_value()) {
232 project_type = *parsed;
233 } else {
234 path = init_type_arg_;
235 }
236 }
237
238 const Manager manager =
239 Manager::init(std::filesystem::path(path), project_type, init_force_);
240 PLOG_INFO << "Initialized a " << templates::to_string(project_type)
241 << "-file NoTeX project in '" << manager.root_dir().string()
242 << "'.";
243 ui::success("Initialized a " +
244 std::string(templates::to_string(project_type)) +
245 "-file NoTeX project in '" + manager.root_dir().string() +
246 "'.");
247}
248
249void Orchestrator::run_theme() const {
250 Manager manager;
251 manager.set_theme(theme_name_);
252 PLOG_INFO << "Theme set to '" << theme_name_ << "' for project at '"
253 << manager.root_dir().string() << "'.";
254 ui::success("Theme set to '" + theme_name_ + "'.");
255}
256
257void Orchestrator::run_add_section() const {
258 Manager manager;
259 manager.add_section(section_title_);
260 PLOG_INFO << "Added section '" << section_title_ << "' to project at '"
261 << manager.root_dir().string() << "'.";
262 ui::success("Added section '" + section_title_ + "'.");
263}
264
265void Orchestrator::run_remove_section() const {
266 Manager manager;
267 if (manager.remove_section(section_number_)) {
268 PLOG_INFO << "Removed section " << section_number_
269 << " from project at '" << manager.root_dir().string()
270 << "'.";
271 ui::success("Removed section " + std::to_string(section_number_) +
272 ".");
273 }
274}
275
276void Orchestrator::run_add_bib() const {
277 Manager manager;
278 manager.add_bibliography();
279 PLOG_INFO << "Bibliography ready: '" << manager.config().bibliography_file
280 << "'.";
281 ui::success("Bibliography ready: '" + manager.config().bibliography_file +
282 "'.");
283}
284
285void Orchestrator::run_remove_bib() const {
286 Manager manager;
287 manager.remove_bibliography();
288 PLOG_INFO << "Removed the bibliography from project at '"
289 << manager.root_dir().string() << "'.";
290 ui::success("Bibliography removed.");
291}
292
293void Orchestrator::run_get() const {
294 const auto print_names = [] {
295 for (const std::string_view name : reference::snippet_names()) {
296 ui::info(" " + std::string(name));
297 }
298 };
299
300 if (get_name_.empty()) {
301 ui::step("Available snippets");
302 print_names();
303 return;
304 }
305
306 const std::vector<reference::Snippet> snippets =
307 reference::find_snippets(get_name_);
308 if (snippets.empty()) {
309 PLOG_WARNING << "'" << get_name_ << "' is not a known snippet name.";
310 ui::warning("'" + get_name_ + "' is not a known snippet name.");
311 std::vector<std::string_view> suggestions;
312 for (const std::string_view name : reference::snippet_names()) {
313 if (name.find(get_name_) != std::string_view::npos) {
314 suggestions.push_back(name);
315 }
316 }
317 ui::step(suggestions.empty() ? "Available snippets" : "Did you mean");
318 if (suggestions.empty()) {
319 print_names();
320 } else {
321 for (const std::string_view name : suggestions) {
322 ui::info(" " + std::string(name));
323 }
324 }
325 return;
326 }
327
328 for (const reference::Snippet& snippet : snippets) {
329 if (snippets.size() > 1) { ui::step(std::string(snippet.name)); }
330 std::cout << snippet.content;
331 }
332}
333
334void Orchestrator::run_checkhealth() const {
335 bool healthy = true;
336 const auto check = [&healthy](bool ok, const std::string& message) {
337 if (ok) {
338 PLOG_INFO << message;
339 ui::success(message);
340 } else {
341 PLOG_ERROR << message;
342 ui::error(message);
343 healthy = false;
344 }
345 };
346
347 ui::step("Environment");
348 try {
349 system::run_command("kpsewhich --version");
350 check(true, "kpsewhich is available.");
351 } catch (const NotexError&) {
352 check(false, "kpsewhich is not available on PATH.");
353 }
354
355 try {
356 const Environment environment;
357 check(true, "TEXMFHOME resolved: " + environment.texmf_home().string());
358
359 const InstallationType type = environment.installation_type();
360 check(type != InstallationType::NONE,
361 std::string("Installation detected: ") +
362 std::string(to_string(type)));
363
364 if (type == InstallationType::LOCAL &&
365 Installer::installation_differs(environment.local_latex_dir(),
366 environment.local_fonts_dir())) {
367 PLOG_WARNING << "The local installation differs from the "
368 "embedded template (customised install?).";
369 ui::warning("The local installation differs from the embedded "
370 "template (customised install?).");
371 } else if (type == InstallationType::GLOBAL &&
373 environment.global_latex_dir(),
374 environment.global_fonts_dir())) {
375 PLOG_WARNING << "The global installation differs from the "
376 "embedded template (customised install?).";
377 ui::warning("The global installation differs from the embedded "
378 "template (customised install?).");
379 }
380 } catch (const EnvironmentError& e) { check(false, e.what()); }
381
382 ui::step("Project");
383 try {
384 const Manager manager;
385 check(true, "Project metadata parses.");
386
387 const std::filesystem::path main_path =
388 manager.root_dir() / manager.config().main_file;
389 check(std::filesystem::exists(main_path),
390 "Main file exists: " + manager.config().main_file);
391
392 if (!manager.config().theme.empty()) {
393 const std::vector<std::string> themes = Manager::available_themes();
394 check(std::find(themes.begin(), themes.end(),
395 manager.config().theme) != themes.end(),
396 "Theme is valid: " + manager.config().theme);
397 }
398
399 const std::vector<std::string> orphans = manager.orphan_sections();
400 if (orphans.empty()) {
401 PLOG_INFO << "Every section file is referenced from the main "
402 "file.";
403 ui::success("Every section file is referenced from the main "
404 "file.");
405 } else {
406 for (const std::string& stem : orphans) {
407 PLOG_WARNING << "Section file '" << stem
408 << "' is not referenced by any \\subfile line.";
409 ui::warning("Section file '" + stem +
410 "' is not referenced by any \\subfile line.");
411 }
412 }
413 } catch (const ProjectNotFoundError&) {
414 ui::info("Not inside a NoTeX project; skipping project checks.");
415 } catch (const ConfigError& e) { check(false, e.what()); }
416
417 if (!healthy) {
418 throw NotexError("checkhealth found one or more problems.",
420 }
421 PLOG_INFO << "checkhealth: everything looks healthy.";
422 ui::success("Everything looks healthy.");
423}
424
425void Orchestrator::run_set() const {
426 Manager manager;
427 manager.set_config_value(set_key_, set_value_);
428 PLOG_INFO << "Set '" << set_key_ << "' to '" << set_value_
429 << "' for project at '" << manager.root_dir().string() << "'.";
430 ui::success("Set '" + set_key_ + "' to '" + set_value_ + "'.");
431}
432
433void Orchestrator::run_ls() const {
434 const Manager manager{std::filesystem::path(ls_path_)};
435 const std::vector<std::filesystem::path> files = manager.project_files();
436
437 if (files.empty()) {
438 ui::info("No project files found.");
439 return;
440 }
441
442 const std::filesystem::path main_path =
443 manager.root_dir() / manager.config().main_file;
444 for (const std::filesystem::path& file : files) {
445 if (file == main_path) {
446 ui::success(file.string() + " (main)");
447 } else if (file.extension() == ".bib") {
448 ui::info(file.string() + " (bibliography)");
449 } else {
450 ui::step(file.string());
451 }
452 }
453}
454
455void Orchestrator::run_uninstall_global() const {
456 const Environment environment;
457 if (Installer::uninstall_global(environment, uninstall_force_)) {
458 PLOG_INFO << "Uninstalled the global NoTeX template.";
459 ui::success("Uninstalled the global NoTeX template.");
460 }
461}
462
463void Orchestrator::run_uninstall_local() const {
464 const std::filesystem::path target_dir(uninstall_path_);
465 if (Installer::uninstall_local(target_dir, uninstall_force_)) {
466 PLOG_INFO << "Uninstalled the local NoTeX template from '"
467 << target_dir.string() << "'.";
468 ui::success("Uninstalled the local NoTeX template from '" +
469 target_dir.string() + "'.");
470 }
471}
472
473void Orchestrator::run_reset() const {
474 Manager manager;
475 const std::string main_file = manager.config().main_file;
476 if (manager.reset(reset_force_)) {
477 PLOG_INFO << "Regenerated '" << main_file
478 << "' from the template for project at '"
479 << manager.root_dir().string() << "'.";
480 ui::success("Regenerated '" + main_file + "' from the template.");
481 }
482}
483
484void Orchestrator::run_delete() const {
485 Manager manager;
486 const std::filesystem::path root = manager.root_dir();
487 if (manager.delete_scaffolding(delete_all_, delete_force_)) {
488 PLOG_INFO << (delete_all_ ? "Deleted the project at '"
489 : "Removed NoTeX's scaffolding from '")
490 << root.string() << "'.";
491 ui::success((delete_all_ ? "Deleted the project at '"
492 : "Removed NoTeX's scaffolding from '") +
493 root.string() + "'.");
494 }
495}
496
497void Orchestrator::register_commands() {
498 // Without this, a token that matches another top-level subcommand's
499 // name (e.g. "info") is parsed as chaining into that sibling
500 // subcommand rather than as consumed by the current one's own
501 // positional argument — so e.g. `notex get info` would silently run
502 // both `get` (with no name) and `info`, rather than passing "info"
503 // as get's snippet name. Capping at one subcommand per invocation
504 // keeps a token that matches a subcommand name from matching as a
505 // second, chained subcommand once the first has already claimed its
506 // positionals (see CLI11's docs on require_subcommand: "limiting
507 // the maximum number allows you to keep arguments that match a
508 // previous subcommand name from matching").
509 app_.require_subcommand(0, 1);
510
511 app_.set_version_flag("--version", std::string(PROJECT_VERSION));
512 // Propagates to notex::ui immediately as the flag is parsed, via
513 // CLI11's per-token ->each() callback, rather than through App's own
514 // deferred callback() mechanism: a subcommand's callback can run
515 // ui::confirm() while argv is still being parsed, so waiting for a
516 // callback that fires only once parsing finishes would be too late.
517 app_.add_flag("-y,--yes", assume_yes_,
518 "Assume 'yes' to every confirmation prompt")
519 ->each([](const std::string&) { ui::set_assume_yes(true); });
520 app_.add_flag("-v,--verbose", verbose_, "Enable verbose output");
521
522 // Subcommand equivalents of the --version/--help flags above, for
523 // users who reach for a subcommand out of habit; both print exactly
524 // what their flag counterpart does.
525 app_.add_subcommand("version", "Show version information and exit")
526 ->callback([this] { run_version(); });
527 app_.add_subcommand("help", "Show this help message and exit")
528 ->callback([this] { run_help(); });
529
530 app_.add_subcommand("info",
531 "Show information about the project, the "
532 "installation, and the template")
533 ->callback([this] { run_info(); });
534
535 auto* clean_command = app_.add_subcommand(
536 "clean", "Remove build artefacts recursively from a directory");
537 clean_command->add_option("path", clean_path_,
538 "Directory to clean (default: current "
539 "directory)");
540 clean_command->add_flag("--dry-run", clean_dry_run_,
541 "Report what would be removed without "
542 "deleting anything");
543 clean_command->callback([this] { run_clean(); });
544
545 auto* install_command = app_.add_subcommand(
546 "install", "Install the NoTeX template globally, or locally when "
547 "a path is given");
548 auto* install_path_option = install_command->add_option(
549 "path", install_path_,
550 "Install locally into this directory instead of globally");
551 install_command->add_flag(
552 "--force", install_force_,
553 "Overwrite a differing existing installation without asking");
554 install_command->callback([this, install_path_option] {
555 if (install_path_option->count() > 0) {
556 run_install_local();
557 } else {
558 run_install_global();
559 }
560 });
561
562 auto* init_command = app_.add_subcommand(
563 "init", "Initialize a new NoTeX project (defaults to multi-file "
564 "in the current directory)");
565 init_command->add_option(
566 "type", init_type_arg_,
567 "'mono' or 'multi' (default: multi); may be omitted, in which "
568 "case a lone positional argument is treated as the path instead");
569 init_command->add_option(
570 "path", init_path_arg_,
571 "Directory to initialize the project in (default: current "
572 "directory)");
573 init_command->add_flag(
574 "--force", init_force_,
575 "Overwrite an existing project instead of refusing to");
576 init_command->callback([this] { run_init(); });
577
578 auto* theme_command =
579 app_.add_subcommand("theme", "Switch the project's theme");
580 theme_command
581 ->add_option("name", theme_name_,
582 "Theme to switch to, e.g. 'light', 'dark', 'tokyo', "
583 "or 'bw'")
584 ->required();
585 theme_command->callback([this] { run_theme(); });
586
587 app_.add_subcommand("checkhealth",
588 "Check the installation and project state")
589 ->callback([this] { run_checkhealth(); });
590
591 auto* set_command = app_.add_subcommand(
592 "set", "Change a project configuration value");
593 set_command
594 ->add_option("key", set_key_,
595 "Configuration key to change: 'main_file', 'theme', "
596 "or 'bibliography_file'")
597 ->required();
598 set_command->add_option("value", set_value_, "New value")->required();
599 set_command->callback([this] { run_set(); });
600
601 auto* ls_command = app_.add_subcommand(
602 "ls", "List the files that belong to a project");
603 ls_command->add_option("path", ls_path_,
604 "Directory to list the project files of "
605 "(default: current directory)");
606 ls_command->callback([this] { run_ls(); });
607
608 auto* get_command = app_.add_subcommand(
609 "get", "Print a ready-to-paste template snippet");
610 get_command->add_option("name", get_name_,
611 "Snippet name (omit to list every available "
612 "name)");
613 get_command->callback([this] { run_get(); });
614
615 // `prune` is a plain alias for `uninstall`: both subcommands share
616 // the same backing storage and dispatch logic, since only one of
617 // them is ever actually invoked in a given run.
618 for (const char* name : {"uninstall", "prune"}) {
619 auto* command = app_.add_subcommand(
620 name, "Remove the NoTeX template installation, globally or "
621 "(given a path) locally");
622 auto* path_option = command->add_option(
623 "path", uninstall_path_,
624 "Uninstall from this directory instead of globally");
625 command->add_flag("--force", uninstall_force_,
626 "Skip the confirmation prompt");
627 command->callback([this, path_option] {
628 if (path_option->count() > 0) {
629 run_uninstall_local();
630 } else {
631 run_uninstall_global();
632 }
633 });
634 }
635
636 auto* reset_command = app_.add_subcommand(
637 "reset", "Regenerate the project's main file from the template");
638 reset_command->add_flag("--force", reset_force_,
639 "Skip the confirmation prompt");
640 reset_command->callback([this] { run_reset(); });
641
642 auto* delete_command = app_.add_subcommand(
643 "delete", "Remove NoTeX's own scaffolding from a project");
644 delete_command->add_flag(
645 "--all", delete_all_,
646 "Also remove every user-authored file (the whole project)");
647 delete_command->add_flag("--force", delete_force_,
648 "Skip the confirmation prompt");
649 delete_command->callback([this] { run_delete(); });
650
651 auto* add_command = app_.add_subcommand(
652 "add", "Add a section or a bibliography to the project");
653 auto* add_section_command = add_command->add_subcommand(
654 "section", "Add a section to the project");
655 add_section_command->add_option("title", section_title_, "Section title")
656 ->required();
657 add_section_command->callback([this] { run_add_section(); });
658 add_command->add_subcommand("bib", "Add a bibliography to the project")
659 ->callback([this] { run_add_bib(); });
660
661 auto* remove_command = app_.add_subcommand(
662 "remove", "Remove a section or the bibliography from the project");
663 auto* remove_section_command = remove_command->add_subcommand(
664 "section", "Remove a section from the project");
665 remove_section_command
666 ->add_option("number", section_number_,
667 "Number of the section to remove, as scanned from "
668 "sections/")
669 ->required();
670 remove_section_command->callback([this] { run_remove_section(); });
671 remove_command
672 ->add_subcommand("bib", "Remove the bibliography from the project")
673 ->callback([this] { run_remove_bib(); });
674}
675
676ExitCode Orchestrator::run(int argc, char** argv) {
677 try {
678 app_.parse(argc, argv);
679 } catch (const CLI::ParseError& e) {
680 // Covers both real usage errors and CLI11's internal use of
681 // ParseError to unwind for --help/--version, so this isn't
682 // necessarily a failure; logged at DEBUG rather than ERROR.
683 const int code = app_.exit(e);
684 PLOG_DEBUG << "CLI parsing exited with code " << code << ": "
685 << e.what();
686 return code == 0 ? ExitCode::SUCCESS : ExitCode::USAGE_ERROR;
687 } catch (const NotexError& e) {
688 PLOG_ERROR << e.what();
689 ui::error(e.what());
690 return e.exit_code();
691 } catch (const std::exception& e) {
692 PLOG_ERROR << e.what();
693 ui::error(e.what());
694 return ExitCode::FAILURE;
695 }
696
697 if (app_.get_subcommands().empty()) {
698 std::cout << app_.help() << std::flush;
699 }
700
701 return ExitCode::SUCCESS;
702}
703
704} // namespace notex
static bool install_local(const std::filesystem::path &target_dir, bool force=false)
Installs the template locally into target_dir.
static bool installation_differs(const std::filesystem::path &latex_dir, const std::filesystem::path &fonts_dir)
Reports whether an installation at latex_dir/fonts_dir differs from what would be freshly installed.
static bool uninstall_global(const Environment &environment, bool force=false)
Removes a global installation: every embedded LaTeX and font file's target directory inside environme...
static bool uninstall_local(const std::filesystem::path &target_dir, bool force=false)
Removes a local installation: target_dir's settings/ and fonts/ directories, then clears installation...
static bool install_global(const Environment &environment, bool force=false)
Installs the template globally, using environment to resolve the target directories inside the TeX tr...
Definition installer.cpp:91
static CleanReport clean(const std::filesystem::path &start_dir, bool dry_run=false)
Recursively removes known LaTeX build artefacts from start_dir.
Definition manager.cpp:410
static std::optional< std::filesystem::path > find_project_root(const std::filesystem::path &start_dir)
Searches upward from start_dir for a .notex/ directory, without requiring one to exist.
Definition manager.cpp:279
static std::vector< std::string > available_themes()
Definition manager.cpp:451
static Manager init(const std::filesystem::path &target_dir, templates::ProjectType project_type=templates::ProjectType::MULTI, bool force=false)
Creates a new NoTeX project inside target_dir.
Definition manager.cpp:374
Base class for every exception raised by the NoTeX core library.
Definition errors.hpp:43
ExitCode run(int argc, char **argv)
Parses the command line and dispatches to the requested command, catching every exception at this sin...
Environment: discovers where TeX lives and whether NoTeX is installed.
Installer: deploys the NoTeX LaTeX template, globally into the TeX tree or locally into a project.
Logging utility with plog initialization.
Manager: represents and operates on a single NoTeX project.
std::vector< std::string_view > snippet_names()
std::vector< Snippet > find_snippets(std::string_view name)
Looks up every snippet registered under name, whether as its canonical name or as an alias.
std::string run_command(const std::string &command)
Runs command through the shell and returns everything it wrote to standard output (and standard error...
Definition system.cpp:19
@ MULTI
main.tex plus a sections/ directory of subfiles.
Definition templates.hpp:24
std::optional< ProjectType > project_type_from_string(std::string_view name)
Parses a project type name.
std::string_view to_string(ProjectType type)
void error(std::string_view message)
Prints an error message, prefixed with a red cross, to stderr.
Definition output.cpp:58
void info(std::string_view message)
Prints a plain informational message to stdout.
Definition output.cpp:66
void success(std::string_view message)
Prints a success message, prefixed with a green checkmark, to stdout.
Definition output.cpp:50
void warning(std::string_view message)
Definition output.cpp:54
void set_assume_yes(bool assume_yes)
Sets whether confirm() should answer "yes" without prompting.
Definition output.cpp:41
void step(std::string_view message)
Prints a step or progress message, prefixed with an arrow, to stdout.
Definition output.cpp:62
ExitCode
Process exit codes returned by the notex executable.
Definition errors.hpp:21
@ FAILURE
An unexpected, unclassified error.
Definition errors.hpp:23
@ USAGE_ERROR
The command line was invalid.
Definition errors.hpp:24
@ SUCCESS
The command completed successfully.
Definition errors.hpp:22
@ LOCAL
A local installation exists (takes precedence over global).
@ GLOBAL
Only a global installation exists.
@ NONE
Neither a local nor a global installation was found.
std::string_view to_string(InstallationType type)
Declares Orchestrator, the application layer that turns command-line input into calls on the notex_co...
Styled terminal output and interactive confirmation prompts.
reference: the table of ready-to-paste snippets served by notex get.
Process execution and environment-variable access.
templates: encoded skeletons used to scaffold new NoTeX projects.