Skip to content

Commit

Permalink
Update (#62)
Browse files Browse the repository at this point in the history
* Update clap and edition

* clippy

* update readme

* Update images for CI

* Update images for build as well
  • Loading branch information
Keats authored Aug 2, 2023
1 parent 8c6aec3 commit bec1aca
Show file tree
Hide file tree
Showing 10 changed files with 731 additions and 329 deletions.
855 changes: 634 additions & 221 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
[package]
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
description = "A simple way to get started with a project by scaffolding from a template powered by the Tera engine"
edition = "2018"
edition = "2021"
keywords = ["tera", "scaffolding", "templating", "generator", "boilerplate"]
license = "MIT"
name = "kickstart"
version = "0.3.0"
version = "0.4.0"

[dependencies]
clap = "2"
clap = { version = "4", features = ["derive"] }
glob = "0.3"
memchr = "2"
regex = "1"
serde = {version = "1", features = ["derive"]}
tera = "1"
heck = "0.4.0"
term = "0.7"
toml = "0.5"
toml = "0.7"
walkdir = "2"

[dev-dependencies]
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,12 @@ You can use these like any other filter, e.g. `{{variable_name | camel_case}}`.

## Changelog

### 0.3.0 (unreleased)
### 0.4.0 (unrelased)

- Add case conversion filter
- Update dependencies

### 0.3.0 (2021-07-10)

- Update dependencies

Expand Down
10 changes: 5 additions & 5 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ stages:
strategy:
matrix:
windows-stable:
imageName: 'vs2017-win2016'
imageName: 'windows-2022'
rustup_toolchain: stable
mac-stable:
imageName: 'macos-10.14'
imageName: 'macos-13'
rustup_toolchain: stable
linux-stable:
imageName: 'ubuntu-latest'
rustup_toolchain: stable
linux-msrv:
imageName: 'ubuntu-latest'
rustup_toolchain: 1.40.0
rustup_toolchain: 1.65.0
pool:
vmImage: $(imageName)
steps:
Expand Down Expand Up @@ -52,11 +52,11 @@ stages:
strategy:
matrix:
windows-stable:
imageName: 'vs2017-win2016'
imageName: 'windows-2022'
rustup_toolchain: stable
target: 'x86_64-pc-windows-msvc'
mac-stable:
imageName: 'macos-10.14'
imageName: 'macos-13'
rustup_toolchain: stable
target: 'x86_64-apple-darwin'
linux-stable:
Expand Down
124 changes: 54 additions & 70 deletions src/bin/kickstart.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,42 @@
use std::env;
use std::error::Error;
use std::path::Path;
use std::path::PathBuf;

use clap::{crate_authors, crate_description, crate_version, App, AppSettings, Arg, SubCommand};
use clap::{Parser, Subcommand};

use kickstart::generation::Template;
use kickstart::terminal;
use kickstart::validate::validate_file;

pub fn build_cli() -> App<'static, 'static> {
App::new("kickstart")
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.setting(AppSettings::SubcommandsNegateReqs)
.arg(
Arg::with_name("template")
.required(true)
.help("Template to use: a local path or a HTTP url pointing to a Git repository"),
)
.arg(
Arg::with_name("output-dir")
.short("o")
.long("output-dir")
.takes_value(true)
.help("Where to output the project: defaults to the current directory"),
)
.arg(
Arg::with_name("sub-dir")
.short("s")
.long("sub-dir")
.takes_value(true)
.help("A subdirectory of the chosen template to use, to allow nested templates."),
)
.arg(
Arg::with_name("no-input")
.long("no-input")
.help("Do not prompt for parameters and only use the defaults from template.toml"),
)
.subcommands(vec![SubCommand::with_name("validate")
.about("Validates that a template.toml is valid")
.arg(Arg::with_name("path").required(true).help("The path to the template.toml"))])
#[derive(Parser)]
#[clap(version, author, about, subcommand_negates_reqs = true)]
pub struct Cli {
/// Template to use: a local path or a HTTP url pointing to a Git repository
#[clap(required = true)]
pub template: Option<String>,

/// Where to output the project: defaults to the current directory
#[clap(short = 'o', long, default_value = ".")]
pub output_dir: PathBuf,

/// A subdirectory of the chosen template to use, to allow nested templates.
#[clap(short = 's', long)]
pub sub_dir: Option<String>,

/// Do not prompt for parameters and only use the defaults from template.toml
#[clap(long, default_value_t = false)]
pub no_input: bool,

#[clap(subcommand)]
pub command: Option<Command>,
}

#[derive(Debug, Subcommand)]
pub enum Command {
/// Validates that a template.toml is valid
Validate {
/// The path to the template.toml
path: PathBuf,
},
}

fn bail(e: &dyn Error) -> ! {
Expand All @@ -54,44 +50,32 @@ fn bail(e: &dyn Error) -> ! {
}

fn main() {
let matches = build_cli().get_matches();
let cli = Cli::parse();

match matches.subcommand() {
("validate", Some(matches)) => {
let errs = match validate_file(matches.value_of("path").unwrap()) {
Ok(e) => e,
Err(e) => bail(&e),
};
if let Some(Command::Validate { path }) = cli.command {
let errs = match validate_file(path) {
Ok(e) => e,
Err(e) => bail(&e),
};

if !errs.is_empty() {
terminal::error("The template.toml is invalid:\n");
for err in errs {
terminal::error(&format!("- {}\n", err));
}
::std::process::exit(1);
} else {
terminal::success("The template.toml file is valid!\n");
if !errs.is_empty() {
terminal::error("The template.toml is invalid:\n");
for err in errs {
terminal::error(&format!("- {}\n", err));
}
::std::process::exit(1);
} else {
terminal::success("The template.toml file is valid!\n");
}
_ => {
// The actual generation call
let template_path = matches.value_of("template").unwrap();
let output_dir = matches
.value_of("output-dir")
.map(|p| Path::new(p).to_path_buf())
.unwrap_or_else(|| env::current_dir().unwrap());
let no_input = matches.is_present("no-input");
let sub_dir = matches.value_of("sub-dir");
} else {
let template = match Template::from_input(&cli.template.unwrap(), cli.sub_dir.as_deref()) {
Ok(t) => t,
Err(e) => bail(&e),
};

let template = match Template::from_input(template_path, sub_dir) {
Ok(t) => t,
Err(e) => bail(&e),
};

match template.generate(&output_dir, no_input) {
Ok(_) => terminal::success("\nEverything done, ready to go!\n"),
Err(e) => bail(&e),
};
}
match template.generate(&cli.output_dir, cli.no_input) {
Ok(_) => terminal::success("\nEverything done, ready to go!\n"),
Err(e) => bail(&e),
};
}
}
15 changes: 7 additions & 8 deletions src/definition.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::collections::{HashMap};
use std::collections::HashMap;

use regex::{Regex, Match};
use serde::Deserialize;
use tera::{Context};
use tera::Context;
use toml::Value;

use crate::errors::{new_error, ErrorKind, Result};
use crate::prompt::{ask_bool, ask_choices, ask_integer, ask_string};
use crate::utils::{render_one_off_template};
use crate::utils::render_one_off_template;

/// A condition for a question to be asked
#[derive(Debug, Clone, PartialEq, Deserialize)]
Expand Down Expand Up @@ -122,10 +121,10 @@ impl TemplateDefinition {
context.insert(key, val);
}

let rendered_default = render_one_off_template(&s, &context, None);
let rendered_default = render_one_off_template(s, &context, None);
match rendered_default {
Err(e) => return Err(e),
Ok(v ) => v,
Ok(v) => v,
}
} else {
s.clone()
Expand Down Expand Up @@ -303,7 +302,7 @@ mod tests {
assert_eq!(tpl.variables.len(), 3);

let res = tpl.ask_questions(true);

assert!(res.is_ok());
let res = res.unwrap();

Expand All @@ -314,6 +313,6 @@ mod tests {
let got_value = res.get("manifest").unwrap();
let expected_value: String = String::from("my_project-other_project-manifest.md");

assert_eq!(got_value, &Value::String(expected_value))
assert_eq!(got_value, &Value::String(expected_value))
}
}
14 changes: 7 additions & 7 deletions src/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,35 @@ pub fn register_all_filters(tera: &mut Tera) {

pub fn upper_camel_case(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
let s = try_get_value!("upper_camel_case", "value", String, value);
Ok(to_value(&s.to_upper_camel_case()).unwrap())
Ok(to_value(s.to_upper_camel_case()).unwrap())
}

pub fn camel_case(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
let s = try_get_value!("camel_case", "value", String, value);
Ok(to_value(&s.to_lower_camel_case()).unwrap())
Ok(to_value(s.to_lower_camel_case()).unwrap())
}

pub fn snake_case(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
let s = try_get_value!("snake_case", "value", String, value);
Ok(to_value(&s.to_snake_case()).unwrap())
Ok(to_value(s.to_snake_case()).unwrap())
}

pub fn kebab_case(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
let s = try_get_value!("kebab_case", "value", String, value);
Ok(to_value(&s.to_kebab_case()).unwrap())
Ok(to_value(s.to_kebab_case()).unwrap())
}

pub fn shouty_snake_case(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
let s = try_get_value!("shouty_snake_case", "value", String, value);
Ok(to_value(&s.to_shouty_snake_case()).unwrap())
Ok(to_value(s.to_shouty_snake_case()).unwrap())
}

pub fn title_case(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
let s = try_get_value!("title_case", "value", String, value);
Ok(to_value(&s.to_title_case()).unwrap())
Ok(to_value(s.to_title_case()).unwrap())
}

pub fn shouty_kebab_case(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
let s = try_get_value!("shouty_kebab_case", "value", String, value);
Ok(to_value(&s.to_shouty_kebab_case()).unwrap())
Ok(to_value(s.to_shouty_kebab_case()).unwrap())
}
20 changes: 11 additions & 9 deletions src/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ use std::process::Command;
use std::str;

use glob::Pattern;
use tera::{Context};
use tera::Context;
use walkdir::WalkDir;

use crate::definition::TemplateDefinition;
use crate::errors::{map_io_err, new_error, ErrorKind, Result};
use crate::utils::{render_one_off_template, create_directory, get_source, is_binary, read_file, write_file, Source};
use crate::utils::{
create_directory, get_source, is_binary, read_file, render_one_off_template, write_file, Source,
};

/// The current template being generated
#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -46,7 +48,7 @@ impl Template {
// on some platforms:
// https://www.reddit.com/r/rust/comments/92mbk5/kickstart_a_scaffolding_tool_to_get_new_projects/e3ahegw
Command::new("git")
.args(&["clone", "--recurse-submodules", remote, &format!("{}", tmp.display())])
.args(["clone", "--recurse-submodules", remote, &format!("{}", tmp.display())])
.output()
.map_err(|err| new_error(ErrorKind::Git { err }))?;
Ok(Template::from_local(&tmp, sub_dir))
Expand Down Expand Up @@ -79,15 +81,15 @@ impl Template {

if !output_dir.exists() {
println!("Creating {:?}", output_dir);
create_directory(&output_dir)?;
create_directory(output_dir)?;
}

// Create the glob patterns of files to copy without rendering first, only once
let patterns: Vec<Pattern> =
definition.copy_without_render.iter().map(|s| Pattern::new(s).unwrap()).collect();

let start_path = if let Some(ref directory) = definition.directory {
self.path.join(&directory)
self.path.join(directory)
} else {
self.path.clone()
};
Expand Down Expand Up @@ -134,19 +136,19 @@ impl Template {
}

// Only pass non-binary files or the files not matching the copy_without_render patterns through Tera
let mut f = File::open(&entry.path())?;
let mut f = File::open(entry.path())?;
let mut buffer = Vec::new();
f.read_to_end(&mut buffer)?;

let no_render = patterns.iter().map(|p| p.matches_path(&real_path)).any(|x| x);

if no_render || is_binary(&buffer) {
map_io_err(fs::copy(&entry.path(), &real_path), entry.path())?;
map_io_err(fs::copy(entry.path(), &real_path), entry.path())?;
continue;
}

let contents = render_one_off_template(
&str::from_utf8(&buffer).unwrap(),
str::from_utf8(&buffer).unwrap(),
&context,
Some(entry.path().to_path_buf()),
)?;
Expand All @@ -158,7 +160,7 @@ impl Template {
if let Some(val) = variables.get(&cleanup.name) {
if *val == cleanup.value {
for p in &cleanup.paths {
let actual_path = render_one_off_template(&p, &context, None)?;
let actual_path = render_one_off_template(p, &context, None)?;
let path_to_delete = output_dir.join(actual_path);
if !path_to_delete.exists() {
continue;
Expand Down
Loading

0 comments on commit bec1aca

Please sign in to comment.