dialogical/
lib.rs

1//!
2//! literally rewriting it in rust because i got tired
3//! of trying to make modules work properly instead of
4//! being able to write actual code
5//!
6//!  \- &Cherry, 11/20/2023
7//!
8
9#![feature(if_let_guard)]
10
11use clap::Parser;
12
13use std::fs::File;
14use std::io::{self, Read, Write};
15use std::path::{Path, PathBuf};
16use std::sync::OnceLock;
17
18mod comptime;
19mod consts;
20mod pages;
21mod parser;
22
23use comptime::{Link, LinkKVPair};
24use parser::Result as ParseResult;
25
26// Re-exports
27pub use pages::{Interaction, InteractionMap, Metaline, Page, PageMeta, Speaker};
28pub use parser::{DgParser, DialogueChoice, DialogueEnding, Label};
29
30pub mod prelude {
31    pub use crate::{
32        DialogueChoice, DialogueEnding, Interaction, InteractionMap, Label, Metaline, Page,
33        PageMeta, Speaker,
34    };
35}
36
37type Error = Box<dyn std::error::Error>;
38
39pub(crate) static SILENT: OnceLock<bool> = OnceLock::new();
40
41macro_rules! log {
42    ($($arg:tt)*) => {
43        if !SILENT.get().unwrap_or(&false) {
44            eprintln!($($arg)*);
45        }
46    };
47}
48
49pub fn deserialize(data: &[u8]) -> Result<InteractionMap, Error> {
50    bincode::deserialize(data).map_err(Into::into)
51}
52
53/// Compile one `.dg` file into a packed `.dgc` via a simple
54/// Rust interface... Pretty much does the same stuff as the
55/// CLI version. Reasonable defaults, but you can always use
56/// `cli_main` directly if you need more control.
57pub fn compile(entry: &str, out: &str) -> Result<(), Error> {
58    let args = Args {
59        file: Some(entry.into()),
60        output: Some(out.into()),
61        silent: true,
62    };
63
64    cli_main(args, None)
65}
66
67pub fn cli_main(args: Args, cwd: Option<&Path>) -> Result<(), Error> {
68    SILENT.set(args.silent).unwrap();
69
70    // TODO error handling for file rw
71    let input_stream: Box<dyn Read> = match args.file {
72        Some(ref file) => Box::new(File::open(file).unwrap()),
73        None => Box::new(io::stdin()),
74    };
75
76    let mut output_stream: Box<dyn Write> = match args.output {
77        Some(file) => Box::new(File::create(file).unwrap()),
78        None => Box::new(io::stdout()),
79    };
80
81    log!("Reading...");
82    let data = io::read_to_string(input_stream)?;
83
84    // priority:
85    // 1. path is the cwd argument passed in, if any
86    // 2. if file argument, `path` is the path of the file
87    // 3. if reading stdin, `path` is the current dir
88    log!("Parsing...");
89    let path = cwd
90        .map(PathBuf::from)
91        .or_else(|| args.file.as_ref().map(PathBuf::from))
92        .unwrap_or_else(|| std::env::current_dir().unwrap());
93
94    let mut parser = DgParser::new(path);
95    let res = parser.parse_all(&data)?;
96
97    log!("Serializing...");
98    let res = bincode::serialize(&res)?;
99
100    log!("Writing...");
101    output_stream.write(&res)?;
102
103    log!("Done!");
104    Ok(())
105}
106
107#[derive(Parser, Debug)]
108#[command(arg_required_else_help(true))]
109#[command(author, version, about)]
110/// P/E/T/S Dialogue Compiler
111pub struct Args {
112    /// The output file, or stdout if not specified
113    #[arg(short, long)]
114    output: Option<String>,
115
116    /// The input file, or stdin if not specified
117    file: Option<String>,
118
119    /// Silences progress "info" stderr messages.
120    #[arg(short, long)]
121    silent: bool,
122}