Remove misc.rs (#491)

Put everything that was in `misc.rs` into their own files, with some opportunistic
refactoring, because why not.
This commit is contained in:
Casey Rodarmor 2019-10-09 01:40:40 -07:00 committed by GitHub
parent ca4f2a44ed
commit 3de31b3c02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 313 additions and 210 deletions

View File

@ -31,9 +31,8 @@ pub(crate) use crate::testing;
// functions
pub(crate) use crate::{
load_dotenv::load_dotenv,
misc::{default, empty},
output::output,
default::default, empty::empty, load_dotenv::load_dotenv, output::output,
write_message_context::write_message_context,
};
// structs and enums
@ -41,12 +40,13 @@ pub(crate) use crate::{
alias::Alias, alias_resolver::AliasResolver, assignment_evaluator::AssignmentEvaluator,
assignment_resolver::AssignmentResolver, color::Color, compilation_error::CompilationError,
compilation_error_kind::CompilationErrorKind, config::Config, config_error::ConfigError,
expression::Expression, fragment::Fragment, function::Function,
function_context::FunctionContext, functions::Functions, interrupt_guard::InterruptGuard,
interrupt_handler::InterruptHandler, justfile::Justfile, lexer::Lexer, output_error::OutputError,
parameter::Parameter, parser::Parser, platform::Platform, position::Position, recipe::Recipe,
recipe_context::RecipeContext, recipe_resolver::RecipeResolver, runtime_error::RuntimeError,
search_error::SearchError, shebang::Shebang, state::State, string_literal::StringLiteral,
count::Count, enclosure::Enclosure, expression::Expression, fragment::Fragment,
function::Function, function_context::FunctionContext, functions::Functions,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, justfile::Justfile,
lexer::Lexer, list::List, output_error::OutputError, parameter::Parameter, parser::Parser,
platform::Platform, position::Position, recipe::Recipe, recipe_context::RecipeContext,
recipe_resolver::RecipeResolver, runtime_error::RuntimeError, search_error::SearchError,
shebang::Shebang, show_whitespace::ShowWhitespace, state::State, string_literal::StringLiteral,
subcommand::Subcommand, token::Token, token_kind::TokenKind, use_color::UseColor,
variables::Variables, verbosity::Verbosity, warning::Warning,
};

View File

@ -1,7 +1,5 @@
use crate::common::*;
use crate::misc::{maybe_s, show_whitespace, write_message_context, Or};
#[derive(Debug, PartialEq)]
pub(crate) struct CompilationError<'a> {
pub(crate) text: &'a str,
@ -82,7 +80,7 @@ impl<'a> Display for CompilationError<'a> {
ref expected,
found,
} => {
writeln!(f, "Expected {}, but found {}", Or(expected), found)?;
writeln!(f, "Expected {}, but found {}", List::or(expected), found)?;
}
DuplicateAlias { alias, first } => {
writeln!(
@ -139,7 +137,7 @@ impl<'a> Display for CompilationError<'a> {
f,
"Found a mix of tabs and spaces in leading whitespace: `{}`\n\
Leading whitespace may consist of tabs or spaces, but not both",
show_whitespace(whitespace)
ShowWhitespace(whitespace)
)?;
}
ExtraLeadingWhitespace => {
@ -152,10 +150,10 @@ impl<'a> Display for CompilationError<'a> {
} => {
writeln!(
f,
"Function `{}` called with {} argument{} but takes {}",
"Function `{}` called with {} {} but takes {}",
function,
found,
maybe_s(found),
Count("argument", found),
expected
)?;
}
@ -164,8 +162,8 @@ impl<'a> Display for CompilationError<'a> {
f,
"Recipe line has inconsistent leading whitespace. \
Recipe started with `{}` but found line with `{}`",
show_whitespace(expected),
show_whitespace(found)
ShowWhitespace(expected),
ShowWhitespace(found)
)?;
}
UnknownAliasTarget { alias, target } => {

25
src/count.rs Normal file
View File

@ -0,0 +1,25 @@
use crate::common::*;
pub struct Count<T: Display>(pub T, pub usize);
impl<T: Display> Display for Count<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.1 == 1 {
write!(f, "{}", self.0)
} else {
write!(f, "{}s", self.0)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn count() {
assert_eq!(Count("dog", 0).to_string(), "dogs");
assert_eq!(Count("dog", 1).to_string(), "dog");
assert_eq!(Count("dog", 2).to_string(), "dogs");
}
}

3
src/default.rs Normal file
View File

@ -0,0 +1,3 @@
pub(crate) fn default<T: Default>() -> T {
Default::default()
}

5
src/empty.rs Normal file
View File

@ -0,0 +1,5 @@
use crate::common::*;
pub(crate) fn empty<T, C: iter::FromIterator<T>>() -> C {
iter::empty().collect()
}

31
src/enclosure.rs Normal file
View File

@ -0,0 +1,31 @@
use crate::common::*;
pub struct Enclosure<T: Display> {
enclosure: &'static str,
value: T,
}
impl<T: Display> Enclosure<T> {
pub fn tick(value: T) -> Enclosure<T> {
Enclosure {
enclosure: "`",
value,
}
}
}
impl<T: Display> Display for Enclosure<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}{}{}", self.enclosure, self.value, self.enclosure)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tick() {
assert_eq!(Enclosure::tick("foo").to_string(), "`foo`")
}
}

View File

@ -22,6 +22,10 @@ mod compilation_error;
mod compilation_error_kind;
mod config;
mod config_error;
mod count;
mod default;
mod empty;
mod enclosure;
mod expression;
mod fragment;
mod function;
@ -31,8 +35,8 @@ mod interrupt_guard;
mod interrupt_handler;
mod justfile;
mod lexer;
mod list;
mod load_dotenv;
mod misc;
mod ordinal;
mod output;
mod output_error;
@ -50,6 +54,7 @@ mod runtime_error;
mod search;
mod search_error;
mod shebang;
mod show_whitespace;
mod state;
mod string_literal;
mod subcommand;
@ -59,6 +64,7 @@ mod use_color;
mod variables;
mod verbosity;
mod warning;
mod write_message_context;
pub use crate::run::run;

123
src/list.rs Normal file
View File

@ -0,0 +1,123 @@
use crate::common::*;
pub struct List<T: Display, I: Iterator<Item = T> + Clone> {
conjunction: &'static str,
values: I,
}
impl<T: Display, I: Iterator<Item = T> + Clone> List<T, I> {
pub fn or<II: IntoIterator<Item = T, IntoIter = I>>(values: II) -> List<T, I> {
List {
conjunction: "or",
values: values.into_iter(),
}
}
pub fn and<II: IntoIterator<Item = T, IntoIter = I>>(values: II) -> List<T, I> {
List {
conjunction: "and",
values: values.into_iter(),
}
}
pub fn or_ticked<II: IntoIterator<Item = T, IntoIter = I>>(
values: II,
) -> List<Enclosure<T>, impl Iterator<Item = Enclosure<T>> + Clone> {
List::or(values.into_iter().map(Enclosure::tick))
}
pub fn and_ticked<II: IntoIterator<Item = T, IntoIter = I>>(
values: II,
) -> List<Enclosure<T>, impl Iterator<Item = Enclosure<T>> + Clone> {
List::and(values.into_iter().map(Enclosure::tick))
}
}
impl<T: Display, I: Iterator<Item = T> + Clone> Display for List<T, I> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut values = self.values.clone().fuse();
if let Some(first) = values.next() {
write!(f, "{}", first)?;
} else {
return Ok(());
}
let second = values.next();
if second.is_none() {
return Ok(());
}
let third = values.next();
if let (Some(second), None) = (second.as_ref(), third.as_ref()) {
write!(f, " {} {}", self.conjunction, second)?;
return Ok(());
}
let mut current = second;
let mut next = third;
loop {
match (current, next) {
(Some(c), Some(n)) => {
write!(f, ", {}", c)?;
current = Some(n);
next = values.next();
}
(Some(c), None) => {
write!(f, ", {} {}", self.conjunction, c)?;
return Ok(());
}
_ => panic!("Iterator was fused, but returned Some after None"),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn or() {
assert_eq!("1", List::or(&[1]).to_string());
assert_eq!("1 or 2", List::or(&[1, 2]).to_string());
assert_eq!("1, 2, or 3", List::or(&[1, 2, 3]).to_string());
assert_eq!("1, 2, 3, or 4", List::or(&[1, 2, 3, 4]).to_string());
}
#[test]
fn and() {
assert_eq!("1", List::and(&[1]).to_string());
assert_eq!("1 and 2", List::and(&[1, 2]).to_string());
assert_eq!("1, 2, and 3", List::and(&[1, 2, 3]).to_string());
assert_eq!("1, 2, 3, and 4", List::and(&[1, 2, 3, 4]).to_string());
}
#[test]
fn or_ticked() {
assert_eq!("`1`", List::or_ticked(&[1]).to_string());
assert_eq!("`1` or `2`", List::or_ticked(&[1, 2]).to_string());
assert_eq!("`1`, `2`, or `3`", List::or_ticked(&[1, 2, 3]).to_string());
assert_eq!(
"`1`, `2`, `3`, or `4`",
List::or_ticked(&[1, 2, 3, 4]).to_string()
);
}
#[test]
fn and_ticked() {
assert_eq!("`1`", List::and_ticked(&[1]).to_string());
assert_eq!("`1` and `2`", List::and_ticked(&[1, 2]).to_string());
assert_eq!(
"`1`, `2`, and `3`",
List::and_ticked(&[1, 2, 3]).to_string()
);
assert_eq!(
"`1`, `2`, `3`, and `4`",
List::and_ticked(&[1, 2, 3, 4]).to_string()
);
}
}

View File

@ -1,167 +0,0 @@
use crate::common::*;
pub(crate) fn show_whitespace(text: &str) -> String {
text
.chars()
.map(|c| match c {
'\t' => '␉',
' ' => '␠',
_ => c,
})
.collect()
}
pub(crate) fn default<T: Default>() -> T {
Default::default()
}
pub(crate) fn empty<T, C: iter::FromIterator<T>>() -> C {
iter::empty().collect()
}
pub(crate) fn ticks<T: Display>(ts: &[T]) -> Vec<Tick<T>> {
ts.iter().map(Tick).collect()
}
pub(crate) fn maybe_s(n: usize) -> &'static str {
if n == 1 {
""
} else {
"s"
}
}
pub(crate) fn conjoin<T: Display>(
f: &mut Formatter,
values: &[T],
conjunction: &str,
) -> Result<(), fmt::Error> {
match values.len() {
0 => {}
1 => write!(f, "{}", values[0])?,
2 => write!(f, "{} {} {}", values[0], conjunction, values[1])?,
_ => {
for (i, item) in values.iter().enumerate() {
write!(f, "{}", item)?;
if i == values.len() - 1 {
} else if i == values.len() - 2 {
write!(f, ", {} ", conjunction)?;
} else {
write!(f, ", ")?
}
}
}
}
Ok(())
}
pub(crate) fn write_message_context(
f: &mut Formatter,
color: Color,
text: &str,
offset: usize,
line: usize,
column: usize,
width: usize,
) -> Result<(), fmt::Error> {
let width = if width == 0 { 1 } else { width };
let line_number = line.ordinal();
match text.lines().nth(line) {
Some(line) => {
let mut i = 0;
let mut space_column = 0;
let mut space_line = String::new();
let mut space_width = 0;
for c in line.chars() {
if c == '\t' {
space_line.push_str(" ");
if i < column {
space_column += 4;
}
if i >= column && i < column + width {
space_width += 4;
}
} else {
if i < column {
space_column += UnicodeWidthChar::width(c).unwrap_or(0);
}
if i >= column && i < column + width {
space_width += UnicodeWidthChar::width(c).unwrap_or(0);
}
space_line.push(c);
}
i += c.len_utf8();
}
let line_number_width = line_number.to_string().len();
writeln!(f, "{0:1$} |", "", line_number_width)?;
writeln!(f, "{} | {}", line_number, space_line)?;
write!(f, "{0:1$} |", "", line_number_width)?;
write!(
f,
" {0:1$}{2}{3:^<4$}{5}",
"",
space_column,
color.prefix(),
"",
space_width,
color.suffix()
)?;
}
None => {
if offset != text.len() {
write!(
f,
"internal error: Error has invalid line number: {}",
line_number
)?
}
}
}
Ok(())
}
pub(crate) struct And<'a, T: 'a + Display>(pub(crate) &'a [T]);
impl<'a, T: Display> Display for And<'a, T> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
conjoin(f, self.0, "and")
}
}
pub(crate) struct Or<'a, T: 'a + Display>(pub(crate) &'a [T]);
impl<'a, T: Display> Display for Or<'a, T> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
conjoin(f, self.0, "or")
}
}
pub(crate) struct Tick<'a, T: 'a + Display>(pub(crate) &'a T);
impl<'a, T: Display> Display for Tick<'a, T> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "`{}`", self.0)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn conjoin_or() {
assert_eq!("1", Or(&[1]).to_string());
assert_eq!("1 or 2", Or(&[1, 2]).to_string());
assert_eq!("1, 2, or 3", Or(&[1, 2, 3]).to_string());
assert_eq!("1, 2, 3, or 4", Or(&[1, 2, 3, 4]).to_string());
}
#[test]
fn conjoin_and() {
assert_eq!("1", And(&[1]).to_string());
assert_eq!("1 and 2", And(&[1, 2]).to_string());
assert_eq!("1, 2, and 3", And(&[1, 2, 3]).to_string());
assert_eq!("1, 2, 3, and 4", And(&[1, 2, 3, 4]).to_string());
}
}

View File

@ -1,6 +1,6 @@
use crate::common::*;
use crate::{interrupt_handler::InterruptHandler, misc::maybe_s};
use crate::interrupt_handler::InterruptHandler;
use unicode_width::UnicodeWidthStr;
fn edit<P: AsRef<OsStr>>(path: P) -> Result<(), i32> {
@ -285,10 +285,10 @@ pub fn run() -> Result<(), i32> {
let min_arguments = recipe.min_arguments();
if min_arguments > 0 {
die!(
"Recipe `{}` cannot be used as default recipe since it requires at least {} argument{}.",
"Recipe `{}` cannot be used as default recipe since it requires at least {} {}.",
recipe.name,
min_arguments,
maybe_s(min_arguments)
Count("argument", min_arguments),
);
}
vec![recipe.name]

View File

@ -1,7 +1,5 @@
use crate::common::*;
use crate::misc::{maybe_s, ticks, write_message_context, And, Or, Tick};
#[derive(Debug)]
pub(crate) enum RuntimeError<'a> {
ArgumentCountMismatch {
@ -102,9 +100,9 @@ impl<'a> Display for RuntimeError<'a> {
} => {
write!(
f,
"Justfile does not contain recipe{} {}.",
maybe_s(recipes.len()),
Or(&ticks(recipes))
"Justfile does not contain {} {}.",
Count("recipe", recipes.len()),
List::or_ticked(recipes),
)?;
if let Some(suggestion) = *suggestion {
write!(f, "\nDid you mean `{}`?", suggestion)?;
@ -113,9 +111,9 @@ impl<'a> Display for RuntimeError<'a> {
UnknownOverrides { ref overrides } => {
write!(
f,
"Variable{} {} overridden on the command line but not present in justfile",
maybe_s(overrides.len()),
And(&overrides.iter().map(Tick).collect::<Vec<_>>())
"{} {} overridden on the command line but not present in justfile",
Count("Variable", overrides.len()),
List::and_ticked(overrides),
)?;
}
ArgumentCountMismatch {
@ -129,29 +127,29 @@ impl<'a> Display for RuntimeError<'a> {
let expected = min;
write!(
f,
"Recipe `{}` got {} argument{} but {}takes {}",
"Recipe `{}` got {} {} but {}takes {}",
recipe,
found,
maybe_s(found),
Count("argument", found),
if expected < found { "only " } else { "" },
expected
)?;
} else if found < min {
write!(
f,
"Recipe `{}` got {} argument{} but takes at least {}",
"Recipe `{}` got {} {} but takes at least {}",
recipe,
found,
maybe_s(found),
Count("argument", found),
min
)?;
} else if found > max {
write!(
f,
"Recipe `{}` got {} argument{} but takes at most {}",
"Recipe `{}` got {} {} but takes at most {}",
recipe,
found,
maybe_s(found),
Count("argument", found),
max
)?;
}

View File

@ -1,6 +1,4 @@
use std::{fmt, io, path::PathBuf};
use crate::misc::And;
use crate::common::*;
pub(crate) enum SearchError {
MultipleCandidates {
@ -29,11 +27,10 @@ impl fmt::Display for SearchError {
f,
"Multiple candidate justfiles found in `{}`: {}",
candidates[0].parent().unwrap().display(),
And(
&candidates
List::and_ticked(
candidates
.iter()
.map(|candidate| format!("`{}`", candidate.file_name().unwrap().to_string_lossy()))
.collect::<Vec<String>>()
.map(|candidate| candidate.file_name().unwrap().to_string_lossy())
),
),
SearchError::NotFound => write!(f, "No justfile found"),

18
src/show_whitespace.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::common::*;
/// String wrapper that uses nonblank characters to display spaces and tabs
pub struct ShowWhitespace<'str>(pub &'str str);
impl<'str> Display for ShowWhitespace<'str> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for c in self.0.chars() {
match c {
'\t' => write!(f, "")?,
' ' => write!(f, "")?,
_ => write!(f, "{}", c)?,
};
}
Ok(())
}
}

View File

@ -1,5 +1,4 @@
use crate::common::*;
use crate::misc::write_message_context;
use Warning::*;

View File

@ -0,0 +1,67 @@
use crate::common::*;
pub(crate) fn write_message_context(
f: &mut Formatter,
color: Color,
text: &str,
offset: usize,
line: usize,
column: usize,
width: usize,
) -> Result<(), fmt::Error> {
let width = if width == 0 { 1 } else { width };
let line_number = line.ordinal();
match text.lines().nth(line) {
Some(line) => {
let mut i = 0;
let mut space_column = 0;
let mut space_line = String::new();
let mut space_width = 0;
for c in line.chars() {
if c == '\t' {
space_line.push_str(" ");
if i < column {
space_column += 4;
}
if i >= column && i < column + width {
space_width += 4;
}
} else {
if i < column {
space_column += UnicodeWidthChar::width(c).unwrap_or(0);
}
if i >= column && i < column + width {
space_width += UnicodeWidthChar::width(c).unwrap_or(0);
}
space_line.push(c);
}
i += c.len_utf8();
}
let line_number_width = line_number.to_string().len();
writeln!(f, "{0:1$} |", "", line_number_width)?;
writeln!(f, "{} | {}", line_number, space_line)?;
write!(f, "{0:1$} |", "", line_number_width)?;
write!(
f,
" {0:1$}{2}{3:^<4$}{5}",
"",
space_column,
color.prefix(),
"",
space_width,
color.suffix()
)?;
}
None => {
if offset != text.len() {
write!(
f,
"internal error: Error has invalid line number: {}",
line_number
)?
}
}
}
Ok(())
}