Add additional continuous integration checks (#574)

Add GitHub Actions checks:

- Clippy is placated
- Rustfmt doesn't produce any changes
- Shell completion scripts are current
This commit is contained in:
Casey Rodarmor 2020-01-15 02:16:13 -08:00 committed by GitHub
parent 85e8015702
commit ed991cb509
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 173 additions and 106 deletions

32
.github/workflows/rust.yaml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Rust
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: clippy, rustfmt
override: true
- name: Version
run: |
rustup --version
cargo --version
cargo clippy --version
- name: Lint
run: cargo clippy
- name: Format
run: cargo fmt -- --check
- name: Completion Scripts
run: |
for script in completions/*; do
shell=${script##*.}
cargo run -- --completions $shell > $script
done
git diff --no-ext-diff --quiet --exit-code

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
.TH JUST "1" "December 2019" "just 0.5.4" "JUST MANUAL"
.TH JUST "1" "January 2020" "just 0.5.4" "JUST MANUAL"
.SH NAME
just \- save and run commands
.SH DESCRIPTION
@ -57,6 +57,10 @@ Print version information
.TP
Print colorful output [default: auto]
[possible values: auto, always, never]
.HP
\fB\-\-completions\fR <SHELL>
.IP
Print shell completion script for <SHELL> [possible values: zsh, bash, fish, powershell, elvish]
.TP
\fB\-f\fR, \fB\-\-justfile\fR <JUSTFILE>
Use <JUSTFILE> as justfile.

View File

@ -1,2 +1,2 @@
tab_spaces = 2
max_width = 100
max_width = 100

View File

@ -73,7 +73,7 @@ impl<'src> Analyzer<'src> {
let mut settings = Settings::new();
for (_, set) in self.sets.into_iter() {
for (_, set) in self.sets {
match set.value {
Setting::Shell(shell) => {
assert!(settings.shell.is_none());
@ -191,7 +191,7 @@ impl<'src> Analyzer<'src> {
// Make sure the target recipe exists
match recipes.get(alias.target.lexeme()) {
Some(target) => Ok(alias.resolve(target.clone())),
Some(target) => Ok(alias.resolve(Rc::clone(target))),
None => Err(token.error(UnknownAliasTarget {
alias: alias.name.lexeme(),
target: alias.target.lexeme(),

View File

@ -12,12 +12,12 @@ pub(crate) struct Color {
}
impl Color {
fn restyle(self, style: Style) -> Color {
Color { style, ..self }
fn restyle(self, style: Style) -> Self {
Self { style, ..self }
}
fn redirect(self, stream: Stream) -> Color {
Color {
fn redirect(self, stream: Stream) -> Self {
Self {
atty: atty::is(stream),
..self
}
@ -31,76 +31,76 @@ impl Color {
}
}
pub(crate) fn fmt(fmt: &Formatter) -> Color {
pub(crate) fn fmt(fmt: &Formatter) -> Self {
if fmt.alternate() {
Color::always()
Self::always()
} else {
Color::never()
Self::never()
}
}
pub(crate) fn auto() -> Color {
Color {
pub(crate) fn auto() -> Self {
Self {
use_color: UseColor::Auto,
..default()
}
}
pub(crate) fn always() -> Color {
Color {
pub(crate) fn always() -> Self {
Self {
use_color: UseColor::Always,
..default()
}
}
pub(crate) fn never() -> Color {
Color {
pub(crate) fn never() -> Self {
Self {
use_color: UseColor::Never,
..default()
}
}
pub(crate) fn stderr(self) -> Color {
pub(crate) fn stderr(self) -> Self {
self.redirect(Stream::Stderr)
}
pub(crate) fn stdout(self) -> Color {
pub(crate) fn stdout(self) -> Self {
self.redirect(Stream::Stdout)
}
pub(crate) fn doc(self) -> Color {
pub(crate) fn doc(self) -> Self {
self.restyle(Style::new().fg(Blue))
}
pub(crate) fn error(self) -> Color {
pub(crate) fn error(self) -> Self {
self.restyle(Style::new().fg(Red).bold())
}
pub(crate) fn warning(self) -> Color {
pub(crate) fn warning(self) -> Self {
self.restyle(Style::new().fg(Yellow).bold())
}
pub(crate) fn banner(self) -> Color {
pub(crate) fn banner(self) -> Self {
self.restyle(Style::new().fg(Cyan).bold())
}
pub(crate) fn command(self) -> Color {
pub(crate) fn command(self) -> Self {
self.restyle(Style::new().bold())
}
pub(crate) fn parameter(self) -> Color {
pub(crate) fn parameter(self) -> Self {
self.restyle(Style::new().fg(Cyan))
}
pub(crate) fn message(self) -> Color {
pub(crate) fn message(self) -> Self {
self.restyle(Style::new().bold())
}
pub(crate) fn annotation(self) -> Color {
pub(crate) fn annotation(self) -> Self {
self.restyle(Style::new().fg(Purple))
}
pub(crate) fn string(self) -> Color {
pub(crate) fn string(self) -> Self {
self.restyle(Style::new().fg(Green))
}
@ -126,8 +126,8 @@ impl Color {
}
impl Default for Color {
fn default() -> Color {
Color {
fn default() -> Self {
Self {
use_color: UseColor::Auto,
atty: false,
style: Style::new(),

View File

@ -237,7 +237,7 @@ impl Config {
}
}
pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult<Config> {
pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult<Self> {
let invocation_directory = env::current_dir().context(config_error::CurrentDir)?;
let verbosity = Verbosity::from_flag_occurrences(matches.occurrences_of(arg::VERBOSE));
@ -368,7 +368,7 @@ impl Config {
|| matches.occurrences_of(arg::SHELL) > 0
|| matches.occurrences_of(arg::SHELL_ARG) > 0;
Ok(Config {
Ok(Self {
dry_run: matches.is_present(arg::DRY_RUN),
highlight: !matches.is_present(arg::NO_HIGHLIGHT),
quiet: matches.is_present(arg::QUIET),
@ -394,7 +394,7 @@ impl Config {
Search::find(&self.search_config, &self.invocation_directory).eprint(self.color)?;
if self.subcommand == Edit {
return self.edit(&search);
return Self::edit(&search);
}
let src = fs::read_to_string(&search.justfile)
@ -415,7 +415,7 @@ impl Config {
}
match &self.subcommand {
Dump => self.dump(justfile),
Dump => Self::dump(justfile),
Completions { shell } => Self::completions(&shell),
Evaluate { overrides } => self.run(justfile, &search, overrides, &Vec::new()),
Run {
@ -423,8 +423,8 @@ impl Config {
overrides,
} => self.run(justfile, &search, overrides, arguments),
List => self.list(justfile),
Show { ref name } => self.show(&name, justfile),
Summary => self.summary(justfile),
Show { ref name } => Self::show(&name, justfile),
Summary => Self::summary(justfile),
Edit | Init => unreachable!(),
}
}
@ -439,12 +439,12 @@ impl Config {
Ok(())
}
fn dump(&self, justfile: Justfile) -> Result<(), i32> {
fn dump(justfile: Justfile) -> Result<(), i32> {
println!("{}", justfile);
Ok(())
}
pub(crate) fn edit(&self, search: &Search) -> Result<(), i32> {
pub(crate) fn edit(search: &Search) -> Result<(), i32> {
let editor = env::var_os("VISUAL")
.or_else(|| env::var_os("EDITOR"))
.unwrap_or_else(|| "vim".into());
@ -601,7 +601,7 @@ impl Config {
}
}
fn show(&self, name: &str, justfile: Justfile) -> Result<(), i32> {
fn show(name: &str, justfile: Justfile) -> Result<(), i32> {
if let Some(alias) = justfile.get_alias(name) {
let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap();
println!("{}", alias);
@ -619,7 +619,7 @@ impl Config {
}
}
fn summary(&self, justfile: Justfile) -> Result<(), i32> {
fn summary(justfile: Justfile) -> Result<(), i32> {
if justfile.count() == 0 {
eprintln!("Justfile contains no recipes.");
} else {

View File

@ -48,8 +48,8 @@ pub(crate) enum ConfigError {
}
impl ConfigError {
pub(crate) fn internal(message: impl Into<String>) -> ConfigError {
ConfigError::Internal {
pub(crate) fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}

View File

@ -1,3 +1,6 @@
// `Self` cannot be used where type takes generic arguments
#![allow(clippy::use_self)]
use crate::common::*;
pub struct Enclosure<T: Display> {
@ -7,7 +10,7 @@ pub struct Enclosure<T: Display> {
impl<T: Display> Enclosure<T> {
pub fn tick(value: T) -> Enclosure<T> {
Enclosure {
Self {
enclosure: "`",
value,
}

View File

@ -66,13 +66,14 @@ impl<'src, 'run> Evaluator<'src, 'run> {
}
}
Expression::Call { thunk } => {
use Thunk::*;
let context = FunctionContext {
dotenv: self.dotenv,
invocation_directory: &self.config.invocation_directory,
search: self.search,
};
use Thunk::*;
match thunk {
Nullary { name, function, .. } => {
function(&context).map_err(|message| RuntimeError::FunctionCall {
@ -183,13 +184,12 @@ impl<'src, 'run> Evaluator<'src, 'run> {
let mut rest = arguments;
for parameter in parameters {
let value = if rest.is_empty() {
match parameter.default {
Some(ref default) => evaluator.evaluate_expression(default)?,
None => {
return Err(RuntimeError::Internal {
message: "missing parameter without default".to_string(),
});
}
if let Some(ref default) = parameter.default {
evaluator.evaluate_expression(default)?
} else {
return Err(RuntimeError::Internal {
message: "missing parameter without default".to_string(),
});
}
} else if parameter.variadic {
let value = rest.to_vec().join(" ");

View File

@ -109,11 +109,12 @@ fn env_var_or_default(
key: &str,
default: &str,
) -> Result<String, String> {
use std::env::VarError::*;
if let Some(value) = context.dotenv.get(key) {
return Ok(value.clone());
}
use std::env::VarError::*;
match env::var(key) {
Err(NotPresent) => Ok(default.to_string()),
Err(NotUnicode(os_string)) => Err(format!(

View File

@ -3,9 +3,9 @@ use crate::common::*;
pub(crate) struct InterruptGuard;
impl InterruptGuard {
pub(crate) fn new() -> InterruptGuard {
pub(crate) fn new() -> Self {
InterruptHandler::instance().block();
InterruptGuard
Self
}
}

View File

@ -7,10 +7,10 @@ pub(crate) struct InterruptHandler {
impl InterruptHandler {
pub(crate) fn install() -> Result<(), ctrlc::Error> {
ctrlc::set_handler(|| InterruptHandler::instance().interrupt())
ctrlc::set_handler(|| Self::instance().interrupt())
}
pub(crate) fn instance() -> MutexGuard<'static, InterruptHandler> {
pub(crate) fn instance() -> MutexGuard<'static, Self> {
lazy_static! {
static ref INSTANCE: Mutex<InterruptHandler> = Mutex::new(InterruptHandler::new());
}
@ -29,8 +29,8 @@ impl InterruptHandler {
}
}
fn new() -> InterruptHandler {
InterruptHandler {
fn new() -> Self {
Self {
blocks: 0,
interrupted: false,
}

View File

@ -51,7 +51,7 @@ impl<'src> Justfile<'src> {
arguments: &'run [String],
) -> RunResult<'run, ()> {
let argvec: Vec<&str> = if !arguments.is_empty() {
arguments.iter().map(|argument| argument.as_str()).collect()
arguments.iter().map(String::as_str).collect()
} else if let Some(recipe) = self.first() {
let min_arguments = recipe.min_arguments();
if min_arguments > 0 {
@ -70,7 +70,7 @@ impl<'src> Justfile<'src> {
let unknown_overrides = overrides
.keys()
.filter(|name| !self.assignments.contains_key(name.as_str()))
.map(|name| name.as_str())
.map(String::as_str)
.collect::<Vec<&str>>();
if !unknown_overrides.is_empty() {

View File

@ -73,15 +73,11 @@ impl<'src> Lexer<'src> {
let len_utf8 = c.len_utf8();
self.token_end.offset += len_utf8;
self.token_end.column += len_utf8;
match c {
'\n' => {
self.token_end.column = 0;
self.token_end.line += 1;
}
_ => {
self.token_end.column += len_utf8;
}
if c == '\n' {
self.token_end.column = 0;
self.token_end.line += 1;
}
self.next = self.chars.next();
@ -203,10 +199,7 @@ impl<'src> Lexer<'src> {
CompilationError { token, kind }
}
fn unterminated_interpolation_error(
&self,
interpolation_start: Token<'src>,
) -> CompilationError<'src> {
fn unterminated_interpolation_error(interpolation_start: Token<'src>) -> CompilationError<'src> {
CompilationError {
token: interpolation_start,
kind: UnterminatedInterpolation,
@ -275,7 +268,7 @@ impl<'src> Lexer<'src> {
}
if let Some(interpolation_start) = self.interpolation_start {
return Err(self.unterminated_interpolation_error(interpolation_start));
return Err(Self::unterminated_interpolation_error(interpolation_start));
}
while self.indented() {
@ -486,7 +479,7 @@ impl<'src> Lexer<'src> {
self.lex_double(InterpolationEnd)
} else if self.at_eol_or_eof() {
// Return unterminated interpolation error that highlights the opening {{
Err(self.unterminated_interpolation_error(interpolation_start))
Err(Self::unterminated_interpolation_error(interpolation_start))
} else {
// Otherwise lex as per normal
self.lex_normal(start)

View File

@ -1,3 +1,37 @@
#![deny(clippy::all, clippy::pedantic, clippy::restriction)]
#![allow(
clippy::print_stdout,
clippy::else_if_without_else,
clippy::use_debug,
clippy::implicit_return,
clippy::if_not_else,
clippy::missing_docs_in_private_items,
clippy::enum_glob_use,
clippy::integer_arithmetic,
clippy::option_unwrap_used,
clippy::indexing_slicing,
clippy::non_ascii_literal,
clippy::missing_inline_in_public_items,
clippy::option_expect_used,
clippy::comparison_chain,
clippy::wildcard_enum_match_arm,
clippy::too_many_lines,
clippy::shadow_unrelated,
clippy::needless_pass_by_value,
clippy::option_map_unwrap_or,
clippy::filter_map,
clippy::result_expect_used,
clippy::unreachable,
clippy::string_add,
clippy::panic,
clippy::match_same_arms
)]
// clippy::option_map_unwrap_or_else,
// clippy::result_expect_used,
// clippy::result_unwrap_used,
// clippy::unreachable
// )]
#[macro_use]
extern crate lazy_static;

View File

@ -1,3 +1,6 @@
// `Self` cannot be used where type takes generic arguments
#![allow(clippy::use_self)]
use crate::common::*;
pub struct List<T: Display, I: Iterator<Item = T> + Clone> {
@ -70,7 +73,7 @@ impl<T: Display, I: Iterator<Item = T> + Clone> Display for List<T, I> {
write!(f, ", {} {}", self.conjunction, c)?;
return Ok(());
}
_ => panic!("Iterator was fused, but returned Some after None"),
_ => unreachable!("Iterator was fused, but returned Some after None"),
}
}
}

View File

@ -17,11 +17,11 @@ pub(crate) enum OutputError {
impl Display for OutputError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
OutputError::Code(code) => write!(f, "Process exited with status code {}", code),
OutputError::Io(ref io_error) => write!(f, "Error executing process: {}", io_error),
OutputError::Signal(signal) => write!(f, "Process terminated by signal {}", signal),
OutputError::Unknown => write!(f, "Process experienced an unknown failure"),
OutputError::Utf8(ref err) => write!(f, "Could not convert process stdout to UTF-8: {}", err),
Self::Code(code) => write!(f, "Process exited with status code {}", code),
Self::Io(ref io_error) => write!(f, "Error executing process: {}", io_error),
Self::Signal(signal) => write!(f, "Process terminated by signal {}", signal),
Self::Unknown => write!(f, "Process experienced an unknown failure"),
Self::Utf8(ref err) => write!(f, "Could not convert process stdout to UTF-8: {}", err),
}
}
}

View File

@ -10,7 +10,7 @@ use crate::common::*;
///
/// - Overrides are of the form `NAME=.*`
///
/// - After overrides comes a single optional search_directory argument.
/// - After overrides comes a single optional search directory argument.
/// This is either '.', '..', or an argument that contains a `/`.
///
/// If the argument contains a `/`, everything before and including
@ -40,9 +40,7 @@ pub struct Positional {
}
impl Positional {
pub fn from_values<'values>(
values: Option<impl IntoIterator<Item = &'values str>>,
) -> Positional {
pub fn from_values<'values>(values: Option<impl IntoIterator<Item = &'values str>>) -> Self {
let mut overrides = Vec::new();
let mut search_directory = None;
let mut arguments = Vec::new();
@ -71,7 +69,7 @@ impl Positional {
}
}
Positional {
Self {
overrides,
search_directory,
arguments,

View File

@ -50,7 +50,7 @@ impl<'src, D> Recipe<'src, D> {
pub(crate) fn max_arguments(&self) -> usize {
if self.parameters.iter().any(|p| p.variadic) {
usize::MAX - 1
usize::max_value() - 1
} else {
self.parameters.len()
}

View File

@ -76,7 +76,7 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
recipe: UnresolvedRecipe<'src>,
) -> CompilationResult<'src, Rc<Recipe<'src>>> {
if let Some(resolved) = self.resolved_recipes.get(recipe.name()) {
return Ok(resolved.clone());
return Ok(Rc::clone(resolved));
}
stack.push(recipe.name());
@ -87,7 +87,7 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
if let Some(resolved) = self.resolved_recipes.get(name) {
// dependency already resolved
dependencies.push(resolved.clone());
dependencies.push(Rc::clone(&resolved));
} else if stack.contains(&name) {
let first = stack[0];
stack.push(first);
@ -114,7 +114,7 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
}
let resolved = Rc::new(recipe.resolve(dependencies)?);
self.resolved_recipes.insert(resolved.clone());
self.resolved_recipes.insert(Rc::clone(&resolved));
stack.pop();
Ok(resolved)
}

View File

@ -72,8 +72,8 @@ pub(crate) enum RuntimeError<'src> {
impl<'src> Error for RuntimeError<'src> {
fn code(&self) -> i32 {
match *self {
Self::Code { code, .. } => code,
Self::Backtick {
Self::Code { code, .. }
| Self::Backtick {
output_error: OutputError::Code(code),
..
} => code,

View File

@ -14,14 +14,14 @@ impl Search {
pub(crate) fn find(
search_config: &SearchConfig,
invocation_directory: &Path,
) -> SearchResult<Search> {
) -> SearchResult<Self> {
match search_config {
SearchConfig::FromInvocationDirectory => {
let justfile = Self::justfile(&invocation_directory)?;
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Search {
Ok(Self {
justfile,
working_directory,
})
@ -34,7 +34,7 @@ impl Search {
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Search {
Ok(Self {
justfile,
working_directory,
})
@ -45,7 +45,7 @@ impl Search {
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Search {
Ok(Self {
justfile,
working_directory,
})
@ -54,7 +54,7 @@ impl Search {
SearchConfig::WithJustfileAndWorkingDirectory {
justfile,
working_directory,
} => Ok(Search {
} => Ok(Self {
justfile: Self::clean(invocation_directory, justfile),
working_directory: Self::clean(invocation_directory, working_directory),
}),
@ -64,14 +64,14 @@ impl Search {
pub(crate) fn init(
search_config: &SearchConfig,
invocation_directory: &Path,
) -> SearchResult<Search> {
) -> SearchResult<Self> {
match search_config {
SearchConfig::FromInvocationDirectory => {
let working_directory = Self::project_root(&invocation_directory)?;
let justfile = working_directory.join(FILENAME);
Ok(Search {
Ok(Self {
justfile,
working_directory,
})
@ -84,7 +84,7 @@ impl Search {
let justfile = working_directory.join(FILENAME);
Ok(Search {
Ok(Self {
justfile,
working_directory,
})
@ -95,7 +95,7 @@ impl Search {
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Search {
Ok(Self {
justfile,
working_directory,
})
@ -104,7 +104,7 @@ impl Search {
SearchConfig::WithJustfileAndWorkingDirectory {
justfile,
working_directory,
} => Ok(Search {
} => Ok(Self {
justfile: Self::clean(invocation_directory, justfile),
working_directory: Self::clean(invocation_directory, working_directory),
}),

View File

@ -85,6 +85,7 @@ impl<'table, V: Keyed<'table> + 'table> IntoIterator for &'table Table<'table, V
type Item = (&'table &'table str, &'table V);
type IntoIter = btree_map::Iter<'table, &'table str, V>;
#[must_use]
fn into_iter(self) -> btree_map::Iter<'table, &'table str, V> {
self.map.iter()
}

View File

@ -8,7 +8,7 @@ pub(crate) enum Verbosity {
}
impl Verbosity {
pub(crate) fn from_flag_occurrences(flag_occurences: u64) -> Verbosity {
pub(crate) fn from_flag_occurrences(flag_occurences: u64) -> Self {
match flag_occurences {
0 => Taciturn,
1 => Loquacious,
@ -19,15 +19,13 @@ impl Verbosity {
pub(crate) fn loquacious(self) -> bool {
match self {
Taciturn => false,
Loquacious => true,
Grandiloquent => true,
Loquacious | Grandiloquent => true,
}
}
pub(crate) fn grandiloquent(self) -> bool {
match self {
Taciturn => false,
Loquacious => false,
Taciturn | Loquacious => false,
Grandiloquent => true,
}
}