pub mod errors;
mod file;
pub mod key;
#[doc(hidden)]
pub mod position;
pub mod settings;
mod string;
use std::collections::HashMap;
use std::io::{BufRead, BufReader};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use errors::{Error, ParseError, Result};
use errors::ErrorType::{MissingArgument, NoCommand, Parse, UnknownCommand};
use key::{Key, parse_keys};
use position::Pos;
use string::{StrExt, check_ident, maybe_word, word, words};
use Command::*;
use Value::*;
#[macro_export]
macro_rules! rtry {
($parse_result:expr, $result:expr) => {
rtry_no_return!($parse_result, $result, { return $parse_result; });
};
}
#[macro_export]
macro_rules! rtry_no_return {
($parse_result:expr, $result:expr, $error_block:block) => {
match $result {
Ok(result) => result,
Err(error) => {
$parse_result.errors.push(error.into());
$error_block
},
}
};
}
pub trait CompletionValues {
fn completion_values() -> Vec<String>;
}
impl CompletionValues for bool {
fn completion_values() -> Vec<String> {
vec!["true".to_string(), "false".to_string()]
}
}
impl CompletionValues for i64 {
fn completion_values() -> Vec<String> {
vec![]
}
}
impl CompletionValues for String {
fn completion_values() -> Vec<String> {
vec![]
}
}
pub trait EnumFromStr
where Self: Sized
{
fn create(variant: &str, argument: &str, prefix: Option<u32>) -> std::result::Result<Self, String>;
fn has_argument(variant: &str) -> std::result::Result<bool, String>;
}
pub trait EnumMetaData {
fn get_metadata() -> HashMap<String, MetaData>;
}
#[derive(Debug)]
pub struct MetaData {
pub completion_hidden: bool,
pub help_text: String,
pub is_special_command: bool,
}
pub struct ParseResult<T> {
pub commands: Vec<Command<T>>,
pub errors: Vec<Error>,
}
impl<T> ParseResult<T> {
#[allow(unknown_lints, new_without_default_derive)]
pub fn new() -> Self {
ParseResult {
commands: vec![],
errors: vec![],
}
}
fn new_with_command(command: Command<T>) -> Self {
ParseResult {
commands: vec![command],
errors: vec![],
}
}
fn merge(&mut self, mut parse_result: ParseResult<T>) {
self.commands.append(&mut parse_result.commands);
self.errors.append(&mut parse_result.errors);
}
}
pub trait SettingCompletion {
fn get_value_completions() -> HashMap<String, Vec<String>>;
}
#[derive(Debug, PartialEq)]
pub enum Command<T> {
App(String),
Custom(T),
Map {
action: String,
keys: Vec<Key>,
mode: String,
},
Set(String, Value),
Unmap {
keys: Vec<Key>,
mode: String,
},
}
#[derive(Default)]
pub struct Config {
pub application_commands: Vec<&'static str>,
pub mapping_modes: Vec<&'static str>,
}
pub struct Parser<T> {
column: usize,
config: Config,
include_path: PathBuf,
line: usize,
_phantom: PhantomData<T>,
}
impl<T: EnumFromStr> Parser<T> {
#[allow(unknown_lints, new_without_default_derive)]
pub fn new() -> Self {
Parser {
column: 1,
config: Config::default(),
include_path: Path::new("./").to_path_buf(),
line: 1,
_phantom: PhantomData,
}
}
pub fn new_with_config(config: Config) -> Self {
Parser {
column: 1,
config: config,
include_path: Path::new("./").to_path_buf(),
line: 1,
_phantom: PhantomData,
}
}
fn check_eol(&self, line: &str, index: usize) -> Result<()> {
if line.len() > index {
let rest = &line[index..];
if let Some(word) = maybe_word(rest) {
let index = word.index;
return Err(ParseError::new(
Parse,
rest.to_string(),
"<end of line>".to_string(),
Pos::new(self.line, self.column + index),
));
}
}
Ok(())
}
fn custom_command(&self, line: &str, word: &str, start_index: usize, index: usize, prefix: Option<u32>)
-> Result<Command<T>>
{
let args =
if line.len() > start_index {
line[start_index..].trim()
}
else if let Ok(true) = T::has_argument(word) {
return Err(self.missing_args(start_index));
}
else {
""
};
if let Ok(command) = T::create(word, args, prefix) {
Ok(Custom(command))
}
else if self.config.application_commands.contains(&word) {
Ok(App(word.to_string()))
}
else {
return Err(ParseError::new(
UnknownCommand,
word.to_string(),
"command or comment".to_string(),
Pos::new(self.line, index + 1)
))
}
}
fn get_rest<'a>(&self, line: &'a str, column: usize) -> Result<&'a str> {
if line.len() > column {
Ok(&line[column..])
}
else {
Err(self.missing_args(column))
}
}
fn line(&mut self, line: &str, prefix: Option<u32>) -> ParseResult<T> {
let mut result = ParseResult::new();
if let Some(word) = maybe_word(line) {
let index = word.index;
let word = word.word;
let start_index = index + word.len() + 1;
self.column = start_index + 1;
let (start3, end3) = word.rsplit_at(3);
let (start5, end5) = word.rsplit_at(5);
if word.starts_with('#') {
return result;
}
if word == "include" {
let rest = rtry!(result, self.get_rest(line, start_index));
self.include_command(rest)
}
else {
let command =
if word == "set" {
let rest = rtry!(result, self.get_rest(line, start_index));
self.set_command(rest)
}
else if end3 == "map" && self.config.mapping_modes.contains(&start3) {
let rest = rtry!(result, self.get_rest(line, start_index));
self.map_command(rest, start3)
}
else if end5 == "unmap" && self.config.mapping_modes.contains(&start5) {
let rest = rtry!(result, self.get_rest(line, start_index));
self.unmap_command(rest, start5)
}
else {
self.custom_command(line, word, start_index, index, prefix)
};
let command = rtry!(result, command);
ParseResult::new_with_command(command)
}
}
else {
result
}
}
fn include_command(&mut self, line: &str) -> ParseResult<T> {
let word = word(line);
let index = word.index;
let word = word.word;
let after_index = index + word.len() + 1;
self.column += after_index;
let mut result = ParseResult::new();
rtry_no_return!(result, self.check_eol(line, after_index), {});
let path = Path::new(&self.include_path).join(word);
let file = rtry!(result, file::open(&path));
let buf_reader = BufReader::new(file);
result.merge(self.parse(buf_reader, None));
result
}
fn map_command(&self, line: &str, mode: &str) -> Result<Command<T>> {
let word = word(line);
let index = word.index;
let word = word.word;
let rest = &line[index + word.len() ..].trim();
if !rest.is_empty() {
Ok(Map {
action: rest.to_string(),
keys: parse_keys(word, self.line, self.column + index)?,
mode: mode.to_string(),
})
}
else {
Err(ParseError::new(
Parse,
"<end of line>".to_string(),
"mapping action".to_string(),
Pos::new(self.line, self.column + line.len())
))
}
}
fn missing_args(&self, column: usize) -> Error {
ParseError::new(
MissingArgument,
"<end of line>".to_string(),
"command arguments".to_string(),
Pos::new(self.line, column)
)
}
pub fn parse<R: BufRead>(&mut self, input: R, prefix: Option<u32>) -> ParseResult<T> {
let mut result = ParseResult::new();
for (line_num, input_line) in input.lines().enumerate() {
self.line = line_num + 1;
let input_line = rtry_no_return!(result, input_line, { continue });
result.merge(self.line(&input_line, prefix));
}
result
}
pub fn parse_line(&mut self, line: &str, prefix: Option<u32>) -> ParseResult<T> {
let mut result = self.parse(line.as_bytes(), prefix);
if result.commands.is_empty() && result.errors.is_empty() {
result.errors.push(ParseError::new(
NoCommand,
"comment or <end of line>".to_string(),
"command".to_string(),
Pos::new(self.line, 1)
));
}
result
}
fn set_command(&mut self, line: &str) -> Result<Command<T>> {
if let Some(words) = words(line, 2) {
let index = words[0].index;
let word = words[0].word;
let identifier = check_ident(word.to_string(), &Pos::new(self.line, self.column + index))?;
let operator = words[1].word;
let operator_index = words[1].index;
if operator == "=" {
let rest = &line[operator_index + 1..];
self.column += operator_index + 1;
Ok(Set(identifier.to_string(), self.value(rest)?))
}
else {
return Err(ParseError::new(
Parse,
operator.to_string(),
"=".to_string(),
Pos::new(self.line, self.column + operator_index)
))
}
}
else {
return Err(ParseError::new(
Parse,
"<end of line>".to_string(),
"=".to_string(),
Pos::new(self.line, self.column + line.len()),
))
}
}
pub fn set_include_path<P: AsRef<Path>>(&mut self, directory: P) {
self.include_path = directory.as_ref().to_path_buf();
}
fn unmap_command(&mut self, line: &str, mode: &str) -> Result<Command<T>> {
let word = word(line);
let index = word.index;
let word = word.word;
let after_index = index + word.len() + 1;
self.column += after_index;
self.check_eol(line, after_index)?;
Ok(Unmap {
keys: parse_keys(word, self.line, self.column + index)?,
mode: mode.to_string(),
})
}
fn value(&self, input: &str) -> Result<Value> {
let string: String = input.chars().take_while(|&character| character != '#').collect();
let string = string.trim();
match string {
"" => Err(ParseError::new(
Parse,
"<end of line>".to_string(),
"value".to_string(),
Pos::new(self.line, self.column + string.len())
)),
"true" => Ok(Bool(true)),
"false" => Ok(Bool(false)),
_ => {
if string.chars().all(|character| character.is_digit(10)) {
Ok(Int(string.parse().unwrap()))
}
else if string.chars().all(|character| character.is_digit(10) || character == '.') {
Ok(Float(string.parse().unwrap()))
}
else {
Ok(Str(input.trim().to_string()))
}
},
}
}
}
pub trait SpecialCommand
where Self: Sized
{
fn identifier_to_command(identifier: char, input: &str) -> std::result::Result<Self, String>;
fn is_identifier(character: char) -> bool;
fn is_incremental(identifier: char) -> bool;
}
#[derive(Debug, PartialEq)]
pub enum Value {
Bool(bool),
Float(f64),
Int(i64),
Str(String),
}
impl Value {
pub fn to_type(&self) -> &str {
match *self {
Bool(_) => "bool",
Float(_) => "float",
Int(_) => "int",
Str(_) => "string",
}
}
}