Introduce a Configuration builder for advanced usage; fix docopt handling.

This commit is contained in:
Niko Matsakis 2016-03-07 05:22:56 -05:00
parent 3dd9b2c157
commit d70cfac7d8
6 changed files with 161 additions and 86 deletions

121
lalrpop/src/api/mod.rs Normal file
View File

@ -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<Error>> {
self.process_dir(try!(current_dir()))
}
/// Process all `.lalrpop` files in `path`.
pub fn process_dir<P:AsRef<Path>>(&self, path: P) -> Result<(), Box<Error>> {
let session = Rc::new(self.session.clone());
try!(build::process_dir(session, path));
Ok(())
}
/// Process the given `.lalrpop` file.
pub fn process_file<P:AsRef<Path>>(&self, path: P) -> Result<(), Box<Error>> {
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<Error>> {
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<Error>> {
Configuration::new().force_build(true).process_current_dir()
}

View File

@ -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<P:AsRef<Path>>(session: &Session, root_dir: P) -> io::Result<()> {
pub fn process_dir<P:AsRef<Path>>(session: Rc<Session>, 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<P:AsRef<Path>>(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<Session>` as an argument.
let session = Rc::new(session.clone());
pub fn process_file<P:AsRef<Path>>(session: Rc<Session>, 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(),

View File

@ -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;

View File

@ -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);

View File

@ -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] <inputs>...
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<String>,
flag_level: Option<LevelFlag>,
flag_force: bool,
flag_help: bool,
flag_color: bool,
flag_comments: bool,
}

View File

@ -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<M>(&self, level: Level, message: M)
where M: FnOnce() -> String
{