summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--src/config.rs168
-rw-r--r--src/filter.rs50
-rw-r--r--src/main.rs44
4 files changed, 219 insertions, 45 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 563d73d..1bf9508 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "rust-httplog"
+name = "ncsautil"
version = "0.0.1"
authors = ["Yorhel <git@yorhel.nl>"]
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..6ab2118
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,168 @@
+use std::collections::HashMap;
+use std::fs::{File,metadata};
+use std::io::{stderr,Write,BufReader,BufRead};
+use std::env::{home_dir,args};
+use std::process::exit;
+
+use super::filter::Filter;
+use getopts::{Matches,Options};
+
+#[derive(Clone,Debug)]
+pub struct Config {
+ pub quiet: bool,
+ pub vhost2ident: bool,
+ pub aliases: HashMap<String, Filter>,
+}
+
+
+impl Config {
+ pub fn new() -> Config {
+ Config {
+ quiet: false,
+ vhost2ident: false,
+ aliases: HashMap::new()
+ }
+ }
+
+ fn add_alias(&mut self, file: &str, i: usize, l: &str) {
+ let mut a = l.splitn(2, ' ');
+ if a.clone().count() != 2 {
+ writeln!(&mut stderr(), "{}:{}: alias needs two arguments", file, i+1).unwrap();
+ return;
+ }
+ let n = a.next().unwrap();
+ // TODO: Verify that the alias only consists of allowed chars
+
+ let f = match Filter::new(a.next().unwrap()) {
+ Err(()) => {
+ writeln!(&mut stderr(), "{}:{}: invalid filter string", file, i+1).unwrap();
+ return
+ },
+ Ok(x) => x
+ };
+
+ self.aliases.insert(String::from_str(n), f);
+ }
+
+ /// Dumps any errors/warnings to stderr, returns false on fatal error.
+ fn load_file(&mut self, file: &str) -> bool {
+ let f = match File::open(file) {
+ Err(x) => {
+ writeln!(&mut stderr(), "Unable to open '{}': {}", file, x).unwrap();
+ return false;
+ },
+ Ok(x) => x
+ };
+ let b = BufReader::new(f);
+ for (i, l) in b.lines().enumerate() {
+ let l = match l {
+ Err(x) => {
+ writeln!(&mut stderr(), "Unable to read from '{}': {}", file, x).unwrap();
+ return false;
+ },
+ Ok(x) => x
+ };
+ if l == "quiet" {
+ self.quiet = true;
+ } else if l == "no-quiet" {
+ self.quiet = false;
+ } else if l == "vhost2ident" {
+ self.vhost2ident = true;
+ } else if l == "no-vhost2ident" {
+ self.vhost2ident = false;
+ } else if l.starts_with("alias ") {
+ self.add_alias(file, i, &l[6..]);
+ } else if !(l.starts_with("#") || l.len() == 0) {
+ writeln!(&mut stderr(), "{}:{}: Unrecognized line: {}", file, i+1, l).unwrap();
+ }
+ }
+ true
+ }
+
+ fn load_config(&mut self, m: &Matches) {
+ if m.opt_present("config") {
+ if !self.load_file(&m.opt_str("config").unwrap()) {
+ exit(1);
+ }
+ } else if !m.opt_present("no-config") {
+ if metadata("/etc/ncsautil").is_ok() {
+ self.load_file("/etc/ncsautil");
+ }
+ let h = home_dir();
+ match h {
+ None => (),
+ Some(mut x) => {
+ x.push(".ncsautil");
+ if metadata(&x).is_ok() {
+ self.load_file(x.to_str().unwrap());
+ }
+ }
+ };
+ }
+ }
+
+ fn parse_opts(&mut self, m: &Matches) {
+ if m.opt_present("vhost2ident") {
+ self.vhost2ident = true;
+ }
+ if m.opt_present("no-vhost2ident") {
+ self.vhost2ident = false;
+ }
+
+ if m.opt_present("quiet") {
+ self.quiet = true;
+ }
+ if m.opt_present("no-quiet") {
+ self.quiet = false;
+ }
+ }
+
+ /// Parses the filter string from the command line
+ fn parse_filter(&self, m: &Matches) -> Filter {
+ if m.free.is_empty() {
+ println!("No filter string given. See --help for options.");
+ exit(1);
+ }
+
+ let filter = &m.free[0];
+ let f = Filter::new(filter);
+ if f.is_err() {
+ println!("Invalid filter '{}'", filter);
+ exit(1);
+ }
+ let mut f = f.unwrap();
+ match f.expand(&self.aliases) {
+ Ok(_) => (),
+ Err(x) => {
+ println!("Invalid filter '{}': {}", filter, x);
+ exit(1);
+ }
+ }
+ f
+ }
+
+ /// Parses command-line arguments and loads any config files. Exits the program on error.
+ pub fn parse_args(&mut self) -> Filter {
+ let mut opts = Options::new();
+ opts.optflag("h", "help", "print this help");
+ opts.optopt( "c", "config", "load alternative config file", "FILE");
+ opts.optflag("", "no-config", "don't load any config file at all");
+ opts.optflag("", "vhost2ident", "normalize hostname into ident field");
+ opts.optflag("", "no-vhost2ident", "");
+ opts.optflag("q", "quiet", "suppress warnings about unrecognized lines");
+ opts.optflag("", "no-quiet", "");
+
+ let mut args = args();
+ let prog = args.next().unwrap();
+ let m = opts.parse(args).unwrap();
+
+ if m.opt_present("h") {
+ print!("{}", opts.usage(format!("Usage: {} [options] <filter>", prog).as_ref()));
+ exit(1);
+ }
+
+ self.load_config(&m);
+ self.parse_opts(&m);
+ self.parse_filter(&m)
+ }
+}
diff --git a/src/filter.rs b/src/filter.rs
index d4ce4bd..4b91e09 100644
--- a/src/filter.rs
+++ b/src/filter.rs
@@ -1,3 +1,4 @@
+use std::collections::HashMap;
use regex::Regex;
use super::Line;
@@ -23,7 +24,7 @@ pub enum FilterStrField {
Ua,
}
-#[derive(Debug)]
+#[derive(Clone,Debug,PartialEq,Eq)]
pub enum Filter {
And(Box<Filter>, Box<Filter>),
Or(Box<Filter>, Box<Filter>),
@@ -31,6 +32,7 @@ pub enum Filter {
SizeCmp(FilterCmp, u64),
StatusCmp(FilterCmp, u16),
Match(FilterStrField, Regex),
+ FilterRef(String),
}
@@ -74,9 +76,8 @@ size -> u64
= u:unsigned m:unit_mult { u * m }
string_part -> &'input str
- = [^"\\]+ { match_str }
- / "\\\"" { "\"" }
- / "\\\\" { "\\" }
+ = "\\\"" { "\"" }
+ / [^"]+ { match_str }
string -> String
= "\"" s:string_part* "\"" { s.concat() }
@@ -84,6 +85,9 @@ string -> String
regex -> super::super::regex::Regex
= s:string {? match super::super::regex::Regex::new(&s) { Ok(x) => Ok(x), Err(_) => Err("Invalid regular expression") } }
+ident -> &'input str
+ = [a-zA-Z0-9_]+ { match_str }
+
ws
= [ ]*
@@ -97,6 +101,7 @@ filter -> super::Filter
/ "size" ws c:cmp ws n:size ws { super::Filter::SizeCmp(c, n) }
/ "status" ws c:cmp ws n:unsigned ws { super::Filter::StatusCmp(c, n as u16) }
/ f:strfield ws "~" ws r:regex ws { super::Filter::Match(f, r) }
+ / ":" f:ident ws { super::Filter::FilterRef(String::from_str(f)) }
"#);
@@ -144,11 +149,42 @@ impl Filter {
Filter::And(ref a, ref b) => a.apply(line) && b.apply(line),
Filter::Or(ref a, ref b) => a.apply(line) || b.apply(line),
Filter::Not(ref a) => !a.apply(line),
- Filter::SizeCmp(c, x) => c.apply(x, line.size),
- Filter::StatusCmp(c, x) => c.apply(x, line.status),
- Filter::Match(f, ref r) => r.is_match(f.getfield(&line))
+ Filter::SizeCmp(c, x) => c.apply(line.size, x),
+ Filter::StatusCmp(c, x) => c.apply(line.status, x),
+ Filter::Match(f, ref r) => r.is_match(f.getfield(&line)),
+ // Can't match without expansion.
+ Filter::FilterRef(_) => unreachable!(),
}
}
+
+ /// Recursively expands any FilterRefs.
+ pub fn expand(&mut self, lst: &HashMap<String, Filter>) -> Result<(), String> {
+ // TODO: Detect and halt on recursion in filters.
+ let mut rep = match *self {
+ Filter::And(ref mut a, ref mut b) => {
+ try!(a.expand(lst));
+ try!(b.expand(lst));
+ return Ok(())
+ },
+ Filter::Or(ref mut a, ref mut b) => {
+ try!(a.expand(lst));
+ try!(b.expand(lst));
+ return Ok(())
+ },
+ Filter::Not(ref mut a) => {
+ try!(a.expand(lst));
+ return Ok(())
+ },
+ Filter::FilterRef(ref n) => match lst.get(n) {
+ None => return Err(format!("unresolved alias: {}", n)),
+ Some(x) => x.clone()
+ },
+ _ => return Ok(()),
+ };
+ try!(rep.expand(lst));
+ *self = rep;
+ Ok(())
+ }
}
diff --git a/src/main.rs b/src/main.rs
index f94107b..50bba93 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,63 +9,33 @@ extern crate regex;
mod line;
mod filter;
-
-pub use line::Line;
+mod config;
use std::io::{stdin,stdout,stderr,Write,BufRead};
-
-fn print_usage(prog: &str, opts: getopts::Options) {
- print!("{}", opts.usage(format!("Usage: {} [options] <filter>", prog).as_ref()));
- std::process::exit(1);
-}
+pub use line::Line;
+use config::Config;
fn main() {
- let mut opts = getopts::Options::new();
- opts.optflag("", "vhost2ident", "normalize hostname into ident field");
- opts.optflag("h", "help", "print this help");
- opts.optflag("q", "quiet", "suppress warnings about unrecognized lines");
- let mut args = std::env::args();
- let prog = args.next().unwrap();
- let m = opts.parse(args).unwrap();
-
- if m.opt_present("h") {
- return print_usage(prog.as_ref(), opts);
- }
-
- let vhost2ident = m.opt_present("vhost2ident");
- let quiet = m.opt_present("q");
- let filter = if m.free.is_empty() {
- return print_usage(prog.as_ref(), opts);
- } else {
- m.free[0].clone()
- };
-
- let filter = match filter::Filter::new(filter.as_ref()) {
- Ok(x) => x,
- Err(_) => {
- println!("Invalid filter '{}'", filter);
- return;
- }
- };
+ let mut c = Config::new();
+ let filter = c.parse_args();
let s = stdin();
let o = stdout();
let mut o = o.lock();
for l in s.lock().lines().map(|x| x.unwrap()) {
-
let mut line = match Line::new(l.as_ref()) {
Ok(x) => x,
Err(_) => {
- if !quiet {
+ if !c.quiet {
writeln!(&mut stderr(), "Unrecognized line: {}", l).unwrap();
}
continue;
}
};
- if vhost2ident {
+ if c.vhost2ident {
line.vhost2ident();
}