mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-31 23:25:17 +00:00
chore: cleanup main.rs
This commit is contained in:
parent
efc833543f
commit
4f4b3684fe
@ -37,7 +37,7 @@ tl = "0.7"
|
||||
thiserror = "2"
|
||||
unicode-width = "0.2"
|
||||
os_pipe = "1.1.5"
|
||||
libc = "0.2.155"
|
||||
libc = "0.2"
|
||||
|
||||
[dependencies.syntect]
|
||||
version = "5.2"
|
||||
|
389
src/main.rs
389
src/main.rs
@ -137,184 +137,164 @@ struct Customizations {
|
||||
code_executor: SnippetExecutor,
|
||||
}
|
||||
|
||||
fn load_customizations(
|
||||
config_file_path: Option<PathBuf>,
|
||||
cwd: &Path,
|
||||
) -> Result<Customizations, Box<dyn std::error::Error>> {
|
||||
let configs_path: PathBuf = match env::var("XDG_CONFIG_HOME") {
|
||||
Ok(path) => Path::new(&path).join("presenterm"),
|
||||
Err(_) => {
|
||||
let Some(project_dirs) = ProjectDirs::from("", "", "presenterm") else {
|
||||
return Ok(Default::default());
|
||||
};
|
||||
project_dirs.config_dir().into()
|
||||
}
|
||||
};
|
||||
let themes = load_themes(&configs_path)?;
|
||||
let config_file_path = config_file_path.unwrap_or_else(|| configs_path.join("config.yaml"));
|
||||
let config = Config::load(&config_file_path)?;
|
||||
let code_executor = SnippetExecutor::new(config.snippet.exec.custom.clone(), cwd.to_path_buf())?;
|
||||
Ok(Customizations { config, themes, code_executor })
|
||||
}
|
||||
impl Customizations {
|
||||
fn load(config_file_path: Option<PathBuf>, cwd: &Path) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let configs_path: PathBuf = match env::var("XDG_CONFIG_HOME") {
|
||||
Ok(path) => Path::new(&path).join("presenterm"),
|
||||
Err(_) => {
|
||||
let Some(project_dirs) = ProjectDirs::from("", "", "presenterm") else {
|
||||
return Ok(Default::default());
|
||||
};
|
||||
project_dirs.config_dir().into()
|
||||
}
|
||||
};
|
||||
let themes = Self::load_themes(&configs_path)?;
|
||||
let config_file_path = config_file_path.unwrap_or_else(|| configs_path.join("config.yaml"));
|
||||
let config = Config::load(&config_file_path)?;
|
||||
let code_executor = SnippetExecutor::new(config.snippet.exec.custom.clone(), cwd.to_path_buf())?;
|
||||
Ok(Customizations { config, themes, code_executor })
|
||||
}
|
||||
|
||||
fn load_themes(config_path: &Path) -> Result<Themes, Box<dyn std::error::Error>> {
|
||||
let themes_path = config_path.join("themes");
|
||||
fn load_themes(config_path: &Path) -> Result<Themes, Box<dyn std::error::Error>> {
|
||||
let themes_path = config_path.join("themes");
|
||||
|
||||
let mut highlight_themes = HighlightThemeSet::default();
|
||||
highlight_themes.register_from_directory(themes_path.join("highlighting"))?;
|
||||
let mut highlight_themes = HighlightThemeSet::default();
|
||||
highlight_themes.register_from_directory(themes_path.join("highlighting"))?;
|
||||
|
||||
let mut presentation_themes = PresentationThemeSet::default();
|
||||
presentation_themes.register_from_directory(&themes_path)?;
|
||||
let mut presentation_themes = PresentationThemeSet::default();
|
||||
presentation_themes.register_from_directory(&themes_path)?;
|
||||
|
||||
let themes = Themes { presentation: presentation_themes, highlight: highlight_themes };
|
||||
Ok(themes)
|
||||
}
|
||||
|
||||
fn display_acknowledgements() {
|
||||
let acknowledgements = include_bytes!("../bat/acknowledgements.txt");
|
||||
println!("{}", String::from_utf8_lossy(acknowledgements));
|
||||
}
|
||||
|
||||
fn make_builder_options(
|
||||
config: &Config,
|
||||
mode: &PresentMode,
|
||||
force_default_theme: bool,
|
||||
render_speaker_notes_only: bool,
|
||||
) -> PresentationBuilderOptions {
|
||||
PresentationBuilderOptions {
|
||||
allow_mutations: !matches!(mode, PresentMode::Export),
|
||||
implicit_slide_ends: config.options.implicit_slide_ends.unwrap_or_default(),
|
||||
command_prefix: config.options.command_prefix.clone().unwrap_or_default(),
|
||||
image_attribute_prefix: config.options.image_attributes_prefix.clone().unwrap_or_else(|| "image:".to_string()),
|
||||
incremental_lists: config.options.incremental_lists.unwrap_or_default(),
|
||||
force_default_theme,
|
||||
end_slide_shorthand: config.options.end_slide_shorthand.unwrap_or_default(),
|
||||
print_modal_background: false,
|
||||
strict_front_matter_parsing: config.options.strict_front_matter_parsing.unwrap_or(true),
|
||||
enable_snippet_execution: config.snippet.exec.enable,
|
||||
enable_snippet_execution_replace: config.snippet.exec_replace.enable,
|
||||
render_speaker_notes_only,
|
||||
auto_render_languages: config.options.auto_render_languages.clone(),
|
||||
let themes = Themes { presentation: presentation_themes, highlight: highlight_themes };
|
||||
Ok(themes)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_default_theme(config: &Config, themes: &Themes, cli: &Cli) -> PresentationTheme {
|
||||
let default_theme_name =
|
||||
cli.theme.as_ref().or(config.defaults.theme.as_ref()).map(|s| s.as_str()).unwrap_or(DEFAULT_THEME);
|
||||
let Some(default_theme) = themes.presentation.load_by_name(default_theme_name) else {
|
||||
let valid_themes = themes.presentation.theme_names().join(", ");
|
||||
let error_message = format!("invalid theme name, valid themes are: {valid_themes}");
|
||||
Cli::command().error(ErrorKind::InvalidValue, error_message).exit();
|
||||
};
|
||||
default_theme
|
||||
struct CoreComponents {
|
||||
third_party: ThirdPartyRender,
|
||||
code_executor: Rc<SnippetExecutor>,
|
||||
resources: Resources,
|
||||
printer: Arc<ImagePrinter>,
|
||||
builder_options: PresentationBuilderOptions,
|
||||
themes: Themes,
|
||||
default_theme: PresentationTheme,
|
||||
config: Config,
|
||||
present_mode: PresentMode,
|
||||
graphics_mode: GraphicsMode,
|
||||
}
|
||||
|
||||
fn select_graphics_mode(cli: &Cli, config: &Config) -> GraphicsMode {
|
||||
if cli.enable_export_mode || cli.export_pdf || cli.generate_pdf_metadata {
|
||||
GraphicsMode::AsciiBlocks
|
||||
} else {
|
||||
let protocol = cli.image_protocol.as_ref().unwrap_or(&config.defaults.image_protocol);
|
||||
match GraphicsMode::try_from(protocol) {
|
||||
Ok(mode) => mode,
|
||||
Err(_) => {
|
||||
Cli::command().error(ErrorKind::InvalidValue, "sixel support was not enabled during compilation").exit()
|
||||
impl CoreComponents {
|
||||
fn new(cli: &Cli, path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let mut resources_path = path.parent().unwrap_or(Path::new("./")).to_path_buf();
|
||||
if resources_path == Path::new("") {
|
||||
resources_path = "./".into();
|
||||
}
|
||||
let resources_path = resources_path.canonicalize().unwrap_or(resources_path);
|
||||
|
||||
let Customizations { config, themes, code_executor } =
|
||||
Customizations::load(cli.config_file.clone().map(PathBuf::from), &resources_path)?;
|
||||
|
||||
let default_theme = Self::load_default_theme(&config, &themes, cli);
|
||||
let force_default_theme = cli.theme.is_some();
|
||||
let present_mode = match (cli.present, cli.enable_export_mode) {
|
||||
(true, _) => PresentMode::Presentation,
|
||||
(false, true) => PresentMode::Export,
|
||||
(false, false) => PresentMode::Development,
|
||||
};
|
||||
|
||||
let mut builder_options =
|
||||
Self::make_builder_options(&config, &present_mode, force_default_theme, cli.listen_speaker_notes);
|
||||
if cli.enable_snippet_execution {
|
||||
builder_options.enable_snippet_execution = true;
|
||||
}
|
||||
if cli.enable_snippet_execution_replace {
|
||||
builder_options.enable_snippet_execution_replace = true;
|
||||
}
|
||||
let graphics_mode = Self::select_graphics_mode(cli, &config);
|
||||
let printer = Arc::new(ImagePrinter::new(graphics_mode.clone())?);
|
||||
let registry = ImageRegistry(printer.clone());
|
||||
let resources = Resources::new(resources_path.clone(), registry.clone());
|
||||
let third_party_config = ThirdPartyConfigs {
|
||||
typst_ppi: config.typst.ppi.to_string(),
|
||||
mermaid_scale: config.mermaid.scale.to_string(),
|
||||
threads: config.snippet.render.threads,
|
||||
};
|
||||
let third_party = ThirdPartyRender::new(third_party_config, registry, &resources_path);
|
||||
let code_executor = Rc::new(code_executor);
|
||||
Ok(Self {
|
||||
third_party,
|
||||
code_executor,
|
||||
resources,
|
||||
printer,
|
||||
builder_options,
|
||||
themes,
|
||||
default_theme,
|
||||
config,
|
||||
present_mode,
|
||||
graphics_mode,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_builder_options(
|
||||
config: &Config,
|
||||
mode: &PresentMode,
|
||||
force_default_theme: bool,
|
||||
render_speaker_notes_only: bool,
|
||||
) -> PresentationBuilderOptions {
|
||||
PresentationBuilderOptions {
|
||||
allow_mutations: !matches!(mode, PresentMode::Export),
|
||||
implicit_slide_ends: config.options.implicit_slide_ends.unwrap_or_default(),
|
||||
command_prefix: config.options.command_prefix.clone().unwrap_or_default(),
|
||||
image_attribute_prefix: config
|
||||
.options
|
||||
.image_attributes_prefix
|
||||
.clone()
|
||||
.unwrap_or_else(|| "image:".to_string()),
|
||||
incremental_lists: config.options.incremental_lists.unwrap_or_default(),
|
||||
force_default_theme,
|
||||
end_slide_shorthand: config.options.end_slide_shorthand.unwrap_or_default(),
|
||||
print_modal_background: false,
|
||||
strict_front_matter_parsing: config.options.strict_front_matter_parsing.unwrap_or(true),
|
||||
enable_snippet_execution: config.snippet.exec.enable,
|
||||
enable_snippet_execution_replace: config.snippet.exec_replace.enable,
|
||||
render_speaker_notes_only,
|
||||
auto_render_languages: config.options.auto_render_languages.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn select_graphics_mode(cli: &Cli, config: &Config) -> GraphicsMode {
|
||||
if cli.enable_export_mode || cli.export_pdf || cli.generate_pdf_metadata {
|
||||
GraphicsMode::AsciiBlocks
|
||||
} else {
|
||||
let protocol = cli.image_protocol.as_ref().unwrap_or(&config.defaults.image_protocol);
|
||||
match GraphicsMode::try_from(protocol) {
|
||||
Ok(mode) => mode,
|
||||
Err(_) => Cli::command()
|
||||
.error(ErrorKind::InvalidValue, "sixel support was not enabled during compilation")
|
||||
.exit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn overflow_validation(mode: &PresentMode, config: &ValidateOverflows) -> bool {
|
||||
match (config, mode) {
|
||||
(ValidateOverflows::Always, _) => true,
|
||||
(ValidateOverflows::Never, _) => false,
|
||||
(ValidateOverflows::WhenPresenting, PresentMode::Presentation) => true,
|
||||
(ValidateOverflows::WhenDeveloping, PresentMode::Development) => true,
|
||||
_ => false,
|
||||
fn load_default_theme(config: &Config, themes: &Themes, cli: &Cli) -> PresentationTheme {
|
||||
let default_theme_name =
|
||||
cli.theme.as_ref().or(config.defaults.theme.as_ref()).map(|s| s.as_str()).unwrap_or(DEFAULT_THEME);
|
||||
let Some(default_theme) = themes.presentation.load_by_name(default_theme_name) else {
|
||||
let valid_themes = themes.presentation.theme_names().join(", ");
|
||||
let error_message = format!("invalid theme name, valid themes are: {valid_themes}");
|
||||
Cli::command().error(ErrorKind::InvalidValue, error_message).exit();
|
||||
};
|
||||
default_theme
|
||||
}
|
||||
}
|
||||
|
||||
fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if cli.generate_config_file_schema {
|
||||
let schema = schemars::schema_for!(Config);
|
||||
serde_json::to_writer_pretty(io::stdout(), &schema).map_err(|e| format!("failed to write schema: {e}"))?;
|
||||
return Ok(());
|
||||
} else if cli.acknowledgements {
|
||||
display_acknowledgements();
|
||||
return Ok(());
|
||||
} else if cli.list_themes {
|
||||
let Customizations { config, themes, .. } =
|
||||
load_customizations(cli.config_file.clone().map(PathBuf::from), ¤t_dir()?)?;
|
||||
let bindings = config.bindings.try_into()?;
|
||||
let demo = ThemesDemo::new(themes, bindings, io::stdout())?;
|
||||
demo.run()?;
|
||||
return Ok(());
|
||||
}
|
||||
struct SpeakerNotesComponents {
|
||||
events_listener: Option<SpeakerNotesEventListener>,
|
||||
events_publisher: Option<SpeakerNotesEventPublisher>,
|
||||
}
|
||||
|
||||
let Some(path) = cli.path.take() else {
|
||||
Cli::command().error(ErrorKind::MissingRequiredArgument, "no path specified").exit();
|
||||
};
|
||||
let mut resources_path = path.parent().unwrap_or(Path::new("./")).to_path_buf();
|
||||
if resources_path == Path::new("") {
|
||||
resources_path = "./".into();
|
||||
}
|
||||
let resources_path = resources_path.canonicalize().unwrap_or(resources_path);
|
||||
|
||||
let Customizations { config, themes, code_executor } =
|
||||
load_customizations(cli.config_file.clone().map(PathBuf::from), &resources_path)?;
|
||||
|
||||
let default_theme = load_default_theme(&config, &themes, &cli);
|
||||
let force_default_theme = cli.theme.is_some();
|
||||
let mode = match (cli.present, cli.enable_export_mode) {
|
||||
(true, _) => PresentMode::Presentation,
|
||||
(false, true) => PresentMode::Export,
|
||||
(false, false) => PresentMode::Development,
|
||||
};
|
||||
let arena = Arena::new();
|
||||
let parser = MarkdownParser::new(&arena);
|
||||
|
||||
let validate_overflows = overflow_validation(&mode, &config.defaults.validate_overflows) || cli.validate_overflows;
|
||||
let mut options = make_builder_options(&config, &mode, force_default_theme, cli.listen_speaker_notes);
|
||||
if cli.enable_snippet_execution {
|
||||
options.enable_snippet_execution = true;
|
||||
}
|
||||
if cli.enable_snippet_execution_replace {
|
||||
options.enable_snippet_execution_replace = true;
|
||||
}
|
||||
let graphics_mode = select_graphics_mode(&cli, &config);
|
||||
let printer = Arc::new(ImagePrinter::new(graphics_mode.clone())?);
|
||||
let registry = ImageRegistry(printer.clone());
|
||||
let resources = Resources::new(resources_path.clone(), registry.clone());
|
||||
let third_party_config = ThirdPartyConfigs {
|
||||
typst_ppi: config.typst.ppi.to_string(),
|
||||
mermaid_scale: config.mermaid.scale.to_string(),
|
||||
threads: config.snippet.render.threads,
|
||||
};
|
||||
let third_party = ThirdPartyRender::new(third_party_config, registry, &resources_path);
|
||||
let code_executor = Rc::new(code_executor);
|
||||
if cli.export_pdf || cli.generate_pdf_metadata {
|
||||
let mut exporter =
|
||||
Exporter::new(parser, &default_theme, resources, third_party, code_executor, themes, options);
|
||||
let mut args = Vec::new();
|
||||
if let Some(theme) = cli.theme.as_ref() {
|
||||
args.extend(["--theme", theme]);
|
||||
}
|
||||
if let Some(path) = cli.config_file.as_ref() {
|
||||
args.extend(["--config-file", path]);
|
||||
}
|
||||
if cli.enable_snippet_execution {
|
||||
args.push("-x");
|
||||
}
|
||||
if cli.enable_snippet_execution_replace {
|
||||
args.push("-X");
|
||||
}
|
||||
if cli.export_pdf {
|
||||
exporter.export_pdf(&path, &args)?;
|
||||
} else {
|
||||
let meta = exporter.generate_metadata(&path)?;
|
||||
println!("{}", serde_json::to_string_pretty(&meta)?);
|
||||
}
|
||||
} else {
|
||||
let full_presentation_path = path.canonicalize().unwrap_or_else(|_| path.clone());
|
||||
impl SpeakerNotesComponents {
|
||||
fn new(cli: &Cli, config: &Config, path: &Path) -> anyhow::Result<Self> {
|
||||
let full_presentation_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
|
||||
let publish_speaker_notes =
|
||||
cli.publish_speaker_notes || (config.speaker_notes.always_publish && !cli.listen_speaker_notes);
|
||||
let events_publisher = publish_speaker_notes
|
||||
@ -328,12 +308,93 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
.then(|| SpeakerNotesEventListener::new(config.speaker_notes.listen_address, full_presentation_path))
|
||||
.transpose()
|
||||
.map_err(|e| anyhow!("failed to create speaker notes listener: {e}"))?;
|
||||
Ok(Self { events_listener, events_publisher })
|
||||
}
|
||||
}
|
||||
|
||||
fn overflow_validation_enabled(mode: &PresentMode, config: &ValidateOverflows) -> bool {
|
||||
match (config, mode) {
|
||||
(ValidateOverflows::Always, _) => true,
|
||||
(ValidateOverflows::Never, _) => false,
|
||||
(ValidateOverflows::WhenPresenting, PresentMode::Presentation) => true,
|
||||
(ValidateOverflows::WhenDeveloping, PresentMode::Development) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_exporter_args(cli: &Cli) -> Vec<&str> {
|
||||
let mut args = Vec::new();
|
||||
if let Some(theme) = cli.theme.as_ref() {
|
||||
args.extend(["--theme", theme]);
|
||||
}
|
||||
if let Some(path) = cli.config_file.as_ref() {
|
||||
args.extend(["--config-file", path]);
|
||||
}
|
||||
if cli.enable_snippet_execution {
|
||||
args.push("-x");
|
||||
}
|
||||
if cli.enable_snippet_execution_replace {
|
||||
args.push("-X");
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if cli.generate_config_file_schema {
|
||||
let schema = schemars::schema_for!(Config);
|
||||
serde_json::to_writer_pretty(io::stdout(), &schema).map_err(|e| format!("failed to write schema: {e}"))?;
|
||||
return Ok(());
|
||||
} else if cli.acknowledgements {
|
||||
let acknowledgements = include_bytes!("../bat/acknowledgements.txt");
|
||||
println!("{}", String::from_utf8_lossy(acknowledgements));
|
||||
return Ok(());
|
||||
} else if cli.list_themes {
|
||||
let Customizations { config, themes, .. } =
|
||||
Customizations::load(cli.config_file.clone().map(PathBuf::from), ¤t_dir()?)?;
|
||||
let bindings = config.bindings.try_into()?;
|
||||
let demo = ThemesDemo::new(themes, bindings, io::stdout())?;
|
||||
demo.run()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(path) = cli.path.clone() else {
|
||||
Cli::command().error(ErrorKind::MissingRequiredArgument, "no path specified").exit();
|
||||
};
|
||||
let CoreComponents {
|
||||
third_party,
|
||||
code_executor,
|
||||
resources,
|
||||
printer,
|
||||
mut builder_options,
|
||||
themes,
|
||||
default_theme,
|
||||
config,
|
||||
present_mode,
|
||||
graphics_mode,
|
||||
} = CoreComponents::new(&cli, &path)?;
|
||||
let arena = Arena::new();
|
||||
let parser = MarkdownParser::new(&arena);
|
||||
let validate_overflows =
|
||||
overflow_validation_enabled(&present_mode, &config.defaults.validate_overflows) || cli.validate_overflows;
|
||||
if cli.export_pdf || cli.generate_pdf_metadata {
|
||||
let mut exporter =
|
||||
Exporter::new(parser, &default_theme, resources, third_party, code_executor, themes, builder_options);
|
||||
if cli.export_pdf {
|
||||
let args = build_exporter_args(&cli);
|
||||
exporter.export_pdf(&path, &args)?;
|
||||
} else {
|
||||
let meta = exporter.generate_metadata(&path)?;
|
||||
println!("{}", serde_json::to_string_pretty(&meta)?);
|
||||
}
|
||||
} else {
|
||||
let SpeakerNotesComponents { events_listener, events_publisher } =
|
||||
SpeakerNotesComponents::new(&cli, &config, &path)?;
|
||||
let command_listener = CommandListener::new(config.bindings.clone(), events_listener)?;
|
||||
|
||||
options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. });
|
||||
builder_options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. });
|
||||
let options = PresenterOptions {
|
||||
builder_options: options,
|
||||
mode,
|
||||
builder_options,
|
||||
mode: present_mode,
|
||||
font_size_fallback: config.defaults.terminal_font_size,
|
||||
bindings: config.bindings,
|
||||
validate_overflows,
|
||||
|
Loading…
x
Reference in New Issue
Block a user