From d70cfac7d8efde7e19ed14d7a7831b46aee3f772 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 7 Mar 2016 05:22:56 -0500 Subject: [PATCH] Introduce a `Configuration` builder for advanced usage; fix docopt handling. --- lalrpop/src/api/mod.rs | 121 ++++++++++++++++++++++++++++++++++++++ lalrpop/src/build/mod.rs | 26 ++------ lalrpop/src/lib.rs | 12 ++-- lalrpop/src/lr1/ascent.rs | 2 +- lalrpop/src/main.rs | 37 ++++++------ lalrpop/src/session.rs | 49 ++++----------- 6 files changed, 161 insertions(+), 86 deletions(-) create mode 100644 lalrpop/src/api/mod.rs diff --git a/lalrpop/src/api/mod.rs b/lalrpop/src/api/mod.rs new file mode 100644 index 0000000..9b3f159 --- /dev/null +++ b/lalrpop/src/api/mod.rs @@ -0,0 +1,121 @@ +use build; +use log::Level; +use session::{ColorConfig, Session}; +use std::default::Default; +use std::env::current_dir; +use std::error::Error; +use std::path::Path; +use std::rc::Rc; + +/// Configure various aspects of how LALRPOP works. +/// Intended for use within a `build.rs` script. +/// To get the default configuration, use `Configuration::new`. +#[derive(Clone, Default)] +pub struct Configuration { + session: Session +} + +impl Configuration { + /// Creates the default configuration; equivalent to `Configuration::default`. + pub fn new() -> Configuration { + Configuration::default() + } + + /// Always use ANSI colors in output, even if output does not appear to be a TTY. + pub fn always_use_colors(&mut self) -> &mut Configuration { + self.session.color_config = ColorConfig::Yes; + self + } + + /// Never use ANSI colors in output, even if output appears to be a TTY. + pub fn never_use_colors(&mut self) -> &mut Configuration { + self.session.color_config = ColorConfig::No; + self + } + + /// Use ANSI colors in output if output appears to be a TTY, but + /// not otherwise. This is the default. + pub fn use_colors_if_tty(&mut self) -> &mut Configuration { + self.session.color_config = ColorConfig::IfTty; + self + } + + /// If true, always convert `.lalrpop` files into `.rs` files, even if the + /// `.rs` file is newer. Default is false. + pub fn force_build(&mut self, val: bool) -> &mut Configuration { + self.session.force_build = val; + self + } + + /// If true, emit comments into the generated code. This makes the + /// generated code significantly larger. Default is false. + pub fn emit_comments(&mut self, val: bool) -> &mut Configuration { + self.session.emit_comments = val; + self + } + + /// Minimal logs: only for errors that halt progress. + pub fn log_quiet(&mut self) -> &mut Configuration { + self.session.log.set_level(Level::Taciturn); + self + } + + /// Informative logs: give some high-level indications of + /// progress (default). + pub fn log_info(&mut self) -> &mut Configuration { + self.session.log.set_level(Level::Informative); + self + } + + /// Verbose logs: more than info, but still not overwhelming. + pub fn log_verbose(&mut self) -> &mut Configuration { + self.session.log.set_level(Level::Verbose); + self + } + + /// Debug logs: better redirect this to a file. Intended for + /// debugging LALRPOP itself. + pub fn log_debug(&mut self) -> &mut Configuration { + self.session.log.set_level(Level::Debug); + self + } + + /// Process all files in the current directory, which -- unless you + /// have changed it -- is typically the root of the crate being compiled. + pub fn process_current_dir(&self) -> Result<(), Box> { + self.process_dir(try!(current_dir())) + } + + /// Process all `.lalrpop` files in `path`. + pub fn process_dir>(&self, path: P) -> Result<(), Box> { + let session = Rc::new(self.session.clone()); + try!(build::process_dir(session, path)); + Ok(()) + } + + /// Process the given `.lalrpop` file. + pub fn process_file>(&self, path: P) -> Result<(), Box> { + let session = Rc::new(self.session.clone()); + try!(build::process_file(session, path)); + Ok(()) + } +} + +/// Process all files in the current directory, which -- unless you +/// have changed it -- is typically the root of the crate being compiled. +/// +/// Equivalent to `Configuration::new().process_current_dir()`. +pub fn process_root() -> Result<(), Box> { + Configuration::new().process_current_dir() +} + +/// Deprecated in favor of `Configuration`. Try: +/// +/// ```rust +/// Configuration::new().force_build(true).process_current_dir() +/// ``` +/// +/// instead. +pub fn process_root_unconditionally() -> Result<(), Box> { + Configuration::new().force_build(true).process_current_dir() +} diff --git a/lalrpop/src/build/mod.rs b/lalrpop/src/build/mod.rs index 175ea8b..08259a1 100644 --- a/lalrpop/src/build/mod.rs +++ b/lalrpop/src/build/mod.rs @@ -17,7 +17,6 @@ use term; use tls::Tls; use tok; -use std::env::current_dir; use std::fs; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -29,33 +28,18 @@ mod fake_term; use self::fake_term::FakeTerminal; -pub fn process_root() -> io::Result<()> { - let session = Session::new(); - process_dir(&session, try!(current_dir())) -} - -pub fn process_root_unconditionally() -> io::Result<()> { - let mut session = Session::new(); - session.set_force_build(); - process_dir(&session, try!(current_dir())) -} - -fn process_dir>(session: &Session, root_dir: P) -> io::Result<()> { +pub fn process_dir>(session: Rc, root_dir: P) -> io::Result<()> { let lalrpop_files = try!(lalrpop_files(root_dir)); for lalrpop_file in lalrpop_files { - try!(process_file(session, lalrpop_file)); + try!(process_file(session.clone(), lalrpop_file)); } Ok(()) } -pub fn process_file>(session: &Session, lalrpop_file: P) -> io::Result<()> { - // Promote the session to an Rc so that we can stick it in TLS. I - // don't want this to be part of LALRPOP's "official" interface - // yet so don't take an `Rc` as an argument. - let session = Rc::new(session.clone()); +pub fn process_file>(session: Rc, lalrpop_file: P) -> io::Result<()> { let lalrpop_file: &Path = lalrpop_file.as_ref(); let rs_file = lalrpop_file.with_extension("rs"); - if session.force_build() || try!(needs_rebuild(&lalrpop_file, &rs_file)) { + if session.force_build || try!(needs_rebuild(&lalrpop_file, &rs_file)) { log!(session, Informative, "processing file `{}`", lalrpop_file.to_string_lossy()); try!(make_read_only(&rs_file, false)); try!(remove_old_file(&rs_file)); @@ -263,7 +247,7 @@ fn report_content(content: &Content) -> term::Result<()> { // FIXME -- can we query the size of the terminal somehow? let canvas = content.emit_to_canvas(80); - let try_colors = match Tls::session().color_config() { + let try_colors = match Tls::session().color_config { ColorConfig::Yes => true, ColorConfig::No => false, ColorConfig::IfTty => atty::is(), diff --git a/lalrpop/src/lib.rs b/lalrpop/src/lib.rs index f867f8f..67a024b 100644 --- a/lalrpop/src/lib.rs +++ b/lalrpop/src/lib.rs @@ -31,6 +31,7 @@ mod rust; #[macro_use] mod log; +mod api; mod ascii_canvas; mod build; mod collections; @@ -51,10 +52,7 @@ mod util; #[cfg(test)] mod generate; #[cfg(test)] mod test_util; -pub use build::process_root; -pub use build::process_root_unconditionally; -pub use build::process_file; -pub use log::Level; -pub use log::Log; -pub use session::ColorConfig; -pub use session::Session; +pub use api::Configuration; +pub use api::process_root; +pub use api::process_root_unconditionally; + diff --git a/lalrpop/src/lr1/ascent.rs b/lalrpop/src/lr1/ascent.rs index 25d32dc..b0b7e70 100644 --- a/lalrpop/src/lr1/ascent.rs +++ b/lalrpop/src/lr1/ascent.rs @@ -247,7 +247,7 @@ impl<'ascent,'grammar,W:Write> RecursiveAscent<'ascent,'grammar,W> { }; // Leave a comment explaining what this state is. - if Tls::session().emit_comments() { + if Tls::session().emit_comments { rust!(self.out, "// State {}", this_index.0); for item in this_state.items.vec.iter() { rust!(self.out, "// {:?}", item); diff --git a/lalrpop/src/main.rs b/lalrpop/src/main.rs index 9e44623..6764117 100644 --- a/lalrpop/src/main.rs +++ b/lalrpop/src/main.rs @@ -3,7 +3,7 @@ extern crate lalrpop; extern crate rustc_serialize; use docopt::Docopt; -use lalrpop::{process_file, Level, ColorConfig, Session}; +use lalrpop::Configuration; use std::env; use std::io::{self, Write}; use std::process; @@ -20,39 +20,34 @@ fn main1() -> io::Result<()> { .and_then(|d| d.argv(env::args()).decode()) .unwrap_or_else(|e| e.exit()); - let mut session = Session::new(); + let mut config = Configuration::new(); match args.flag_level.unwrap_or(LevelFlag::Info) { - LevelFlag::Quiet => session.set_log_level(Level::Taciturn), - LevelFlag::Info => session.set_log_level(Level::Informative), - LevelFlag::Verbose => session.set_log_level(Level::Verbose), - LevelFlag::Debug => session.set_log_level(Level::Debug), - } + LevelFlag::Quiet => config.log_quiet(), + LevelFlag::Info => config.log_info(), + LevelFlag::Verbose => config.log_verbose(), + LevelFlag::Debug => config.log_debug(), + }; if args.flag_force { - session.set_force_build(); - } - - if args.flag_help { - try!(writeln!(stderr, "{}", USAGE)); - process::exit(1); + config.force_build(true); } if args.flag_color { - session.set_color_config(ColorConfig::Yes); + config.always_use_colors(); } if args.flag_comments { - session.set_emit_comments(); + config.emit_comments(true); } if args.arg_inputs.len() == 0 { - try!(writeln!(stderr, "Error: no input files specified! Try -h for help.")); + try!(writeln!(stderr, "Error: no input files specified! Try --help for help.")); process::exit(1); } for arg in args.arg_inputs { - match process_file(&session, &arg) { + match config.process_file(&arg) { Ok(()) => { } Err(err) => { try!(writeln!(stderr, "Error encountered processing `{}`: {}", @@ -66,10 +61,13 @@ fn main1() -> io::Result<()> { } const USAGE: &'static str = " -Usage: lalrpop [options] ... +Usage: lalrpop [options] inputs... + lalrpop --help + +Convert each of the given inputs (which should be a `.lalrpop` file) +into a `.rs` file, just as a `build.rs` script using LALRPOP would do. Options: - -h, --help Show this message. -l, --level LEVEL Set the debug level. (Default: info) Valid values: quiet, info, verbose, debug. -f, --force Force execution, even if the .lalrpop file is older than the .rs file. @@ -82,7 +80,6 @@ struct Args { arg_inputs: Vec, flag_level: Option, flag_force: bool, - flag_help: bool, flag_color: bool, flag_comments: bool, } diff --git a/lalrpop/src/session.rs b/lalrpop/src/session.rs index 6932538..a924053 100644 --- a/lalrpop/src/session.rs +++ b/lalrpop/src/session.rs @@ -1,7 +1,14 @@ +//! Internal configuration and session-specific settings. This is similar +//! to `configuration::Configuration`, but it is not exported outside the +//! crate. Note that all fields are public and so forth for convenience. + use std::default::Default; use style::{self, Style}; use log::{Log, Level}; +// These two, ubiquitous types are defined here so that their fields can be private +// across crate, but visible within the crate: + #[derive(Copy, Clone)] pub enum ColorConfig { /// Use ANSI colors. @@ -20,20 +27,20 @@ pub enum ColorConfig { /// expected to use it. #[derive(Clone)] pub struct Session { - log: Log, + pub log: Log, - force_build: bool, + pub force_build: bool, /// Emit comments in generated code explaining the states and so /// forth. - emit_comments: bool, + pub emit_comments: bool, - color_config: ColorConfig, + pub color_config: ColorConfig, /// Stop after you find `max_errors` errors. If this value is 0, /// report *all* errors. Note that we MAY always report more than /// this value if we so choose. - max_errors: usize, + pub max_errors: usize, // Styles to use when formatting error reports @@ -102,44 +109,12 @@ impl Session { } } - pub fn color_config(&self) -> ColorConfig { - self.color_config - } - - pub fn set_color_config(&mut self, config: ColorConfig) { - self.color_config = config; - } - - pub fn set_force_build(&mut self) { - self.force_build = true; - } - - pub fn set_emit_comments(&mut self) { - self.emit_comments = true; - } - - pub fn set_max_errors(&mut self, errors: usize) { - self.max_errors = errors; - } - - pub fn set_log_level(&mut self, level: Level) { - self.log.set_level(level); - } - /// Indicates whether we should stop after `actual_errors` number /// of errors have been reported. pub fn stop_after(&self, actual_errors: usize) -> bool { self.max_errors != 0 && actual_errors >= self.max_errors } - pub fn force_build(&self) -> bool { - self.force_build - } - - pub fn emit_comments(&self) -> bool { - self.emit_comments - } - pub fn log(&self, level: Level, message: M) where M: FnOnce() -> String {