Skip to content

Commit

Permalink
Command-line options for network IO and input from stdin
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Kay committed Jun 16, 2023
1 parent 1de2d2a commit 331eaaa
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "papyri-lang"
version = "0.6.0"
version = "0.7.0"
edition = "2021"
license = "MIT"
description = "Compiler for the Papyri markup language"
Expand Down
52 changes: 50 additions & 2 deletions src/bin/papyri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ fn main() {
std::process::exit(0);
}

if args.paths.is_empty() {
args.silent |= args.use_stdin;

if args.paths.is_empty() && !args.use_stdin {
args.paths.push(".".to_string());
}

Expand Down Expand Up @@ -48,10 +50,18 @@ struct ProgramArgs {
///Compile to text instead of HTML
text: bool,

#[arg(short = "i", long = "stdin")]
///Take input from stdin and print result to stdout
use_stdin: bool,

#[arg(short, long = "out")]
///Output directory (default is the current directory)
out_dir: Option<std::path::PathBuf>,

#[arg(long = "allow-http")]
///Allow HTTP requests in the standard library
allow_http: bool,

///The Papyri source file(s) to compile. If none are specified, the current
///directory is searched for Papyri source files.
paths: Vec<String>,
Expand All @@ -78,11 +88,49 @@ impl Main {
errors::ReportingLevel::Warning
};

let ctx = compiler::Context::new(reporting_level, options.out_dir.as_deref());
let http_client = options.allow_http.then(|| {
reqwest::blocking::Client::builder()
.user_agent("Mozilla/5.0 (compatible) Papyri")
.build()
.unwrap_or_else(|e| errors::ice(&format!("Failed to build HTTP client: {e}")))
});

let ctx = compiler::Context::new(reporting_level, options.out_dir.as_deref(), http_client);
Main {options, ctx}
}

fn run_stdin(&mut self) -> Result<(), String> {
if !self.options.paths.is_empty() {
return Err("Error: --stdin option cannot be used with file inputs".to_string());
} else if self.options.out_dir.is_some() {
return Err("Error: --out option cannot be used with --stdin".to_string());
}

use std::io::Read;

let mut src_buf = Vec::new();
std::io::stdin()
.read_to_end(&mut src_buf)
.map_err(|e| format!("Input error: {e}"))?;
let src_str = String::from_utf8(src_buf)
.map_err(|e| format!("Input error: was not valid UTF-8 ({e})"))?;

let result = self.ctx.compile_synthetic("<stdin>", src_str.as_ref());

if self.ctx.diagnostics.is_empty() {
self.ctx.render(&result.out, !self.options.text, &mut std::io::stdout())
.map_err(|e| format!("Failed to write to stdout: {e}"))
} else {
self.ctx.diagnostics.print_to_stderr();
Err(self.ctx.diagnostics.summary())
}
}

fn run(&mut self) -> Result<(), String> {
if self.options.use_stdin {
return self.run_stdin();
}

let in_dir = std::env::current_dir()
.and_then(fs::canonicalize)
.map_err(|e| format!("File error: {e} in current working directory"))?;
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ pub struct Context {

/// The output files collector for this compiler context, if it has one.
pub out_files: Option<OutFiles<HTML>>,

/// An HTTP client used for net requests, if enabled.
pub http_client: Option<reqwest::blocking::Client>,
}

impl Context {
/// Creates a new compiler context. This includes compiling the standard
/// library.
pub fn new(reporting_level: errors::ReportingLevel, out_dir: Option<&std::path::Path>) -> Context {
pub fn new(reporting_level: errors::ReportingLevel, out_dir: Option<&std::path::Path>, http_client: Option<reqwest::blocking::Client>) -> Context {
let natives = NativeDefs::build();
let natives_frame = natives.to_frame().to_inactive();
let mut ctx = Context {
Expand All @@ -52,6 +55,7 @@ impl Context {
natives_frame,
unique_ids: text::UniqueIDGenerator::new(),
out_files: out_dir.map(OutFiles::new),
http_client,
};
ctx.compile_stdlib();
ctx
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ impl Context {
self._compile(src, taginfo::ContentKind::REQUIRE_P)
}

/// Compiles Papyri source given as a string.
pub fn compile_synthetic(&mut self, name: &str, src_str: &str) -> CompileResult {
let src = self.source_files.load_synthetic(name, src_str);
self._compile(src, taginfo::ContentKind::REQUIRE_P)
}

/// Loads a Papyri source file from the filesystem and compiles it. This
/// only fails if the source file cannot be read; any other errors which
/// occur during compilation are reported through `self.diagnostics`.
Expand Down
10 changes: 5 additions & 5 deletions src/compiler/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,11 +532,11 @@ impl <'a> Compiler<'a> {
}

fn native_fetch_impl(&mut self, path: RcStr) -> errors::PapyriResult<String> {
Ok(reqwest::blocking::Client::builder()
.user_agent("Mozilla/5.0 (compatible) Papyri")
.build()
.map_err(errors::RuntimeError::NetworkError)?
.get(path.as_ref())
let Some(client) = &self.ctx.http_client else {
return Err(errors::RuntimeError::NetworkDisabled.into());
};

Ok(client.get(path.as_ref())
.send()
.map_err(errors::RuntimeError::NetworkError)?
.text()
Expand Down
2 changes: 2 additions & 0 deletions src/errors/runtime_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub enum RuntimeError {
WriteFileNotAllowed,
ParseHtmlError(std::rc::Rc<str>),
NetworkError(reqwest::Error),
NetworkDisabled,
}

impl std::fmt::Display for NameError {
Expand Down Expand Up @@ -72,6 +73,7 @@ impl std::fmt::Display for RuntimeError {
RuntimeError::WriteFileNotAllowed => f.write_str("no output directory for '@file::write'; use '--out'"),
RuntimeError::ParseHtmlError(e) => write!(f, "failed to parse HTML ({e})"),
RuntimeError::NetworkError(e) => write!(f, "network error ({e})"),
RuntimeError::NetworkDisabled => write!(f, "network access is not enabled"),
}
}
}
8 changes: 5 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ pub mod utils;
/// Compiles Papyri source given as a string into HTML, as a string. If any
/// errors or warnings occur during compilation, the diagnostics are returned
/// instead.
///
/// File and network IO is disabled. For more control over the compilation
/// context, use `compiler::Context::compile_synthetic`.
pub fn compile_str(src: &str) -> Result<String, errors::Diagnostics> {
let mut ctx = compiler::Context::new(errors::ReportingLevel::Warning, None);
let src = ctx.source_files.load_synthetic("<string>", src);
let result = ctx.compile(src);
let mut ctx = compiler::Context::new(errors::ReportingLevel::Warning, None, None);
let result = ctx.compile_synthetic("<string>", src);

if ctx.diagnostics.is_empty() {
let mut out = Vec::new();
Expand Down

0 comments on commit 331eaaa

Please sign in to comment.