This commit is contained in:
Casey Rodarmor 2016-09-27 22:49:17 -07:00
parent a0d5b83a80
commit 114f6b7bdc
5 changed files with 186 additions and 61 deletions

38
Cargo.lock generated
View File

@ -1,4 +1,42 @@
[root]
name = "j"
version = "0.1.0"
dependencies = [
"brev 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "brev"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "glob"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempdir"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -4,3 +4,5 @@ version = "0.1.0"
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
[dependencies]
brev = "0.1.6"

31
README.md Normal file
View File

@ -0,0 +1,31 @@
j
=
`j` is a handy way to run project-specific commands.
`j` looks upward from the current directory for a file called `justfile` and then runs make with that file as the makefile. `j` also sets the current working directory to where it found the justfile, so your commands are executed from the root of your project and not from whatever subdirectory you happen to be in.
With no arguments it runs the default recipe:
`j`
Adding one argument specifies the recipe:
`j compile`
Arguments after `--` are exported as `ARG0, ARG1, ..., ARGN`, which can be used in the justfile. To run recipe `compile` and export `ARG0=bar` and `ARG1=baz`:
`just compile -- bar baz`
By way of example, the included justfile has a pinch of fanciful fluff.
getting j
---------
J is distributed via `cargo`, rust's package manager.
1. Get cargo at [rustup.rs](https://www.rustup.rs)
2. Run `cargo install j`
3. Add `~/.cargo/bin` to your PATH
`j` depends on make to actually run commands, but hopefully if you're on a unix, make is already installed. If not, you can get it from friendly local package manager.

View File

@ -1,2 +1,33 @@
default:
cargo run hello -- -- bar a b c
# just a makefile with no special magic
test:
cargo build
./target/debug/j args -- a b c
# list all recipies
list:
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | xargs
args:
@echo "I got some arguments: ARG0=${ARG0} ARG1=${ARG1} ARG2=${ARG2}"
# make a quine and compile it
quine: create compile
# create our quine
create:
mkdir -p tmp
echo 'int printf(const char*, ...); int main() { char *s = "int printf(const char*, ...); int main() { char *s = %c%s%c; printf(s, 34, s, 34); return 0; }"; printf(s, 34, s, 34); return 0; }' > tmp/gen0.c
# make sure it's really a quine
compile:
cc tmp/gen0.c -o tmp/gen0
./tmp/gen0 > tmp/gen1.c
cc tmp/gen1.c -o tmp/gen1
./tmp/gen1 > tmp/gen2.c
diff tmp/gen1.c tmp/gen2.c
@echo 'It was a quine!'
# clean up
clean:
rm -r tmp

View File

@ -1,84 +1,107 @@
use std::io::prelude::*;
#[macro_use]
extern crate brev;
fn can(command: &str) -> bool {
if let Ok(paths) = std::env::var("PATH") {
for path in paths.split(":") {
let candidate = format!("{}/{}", path, command);
if isfile(&candidate) {
return true;
}
}
}
false
#[derive(PartialEq, Clone, Copy)]
enum Make {
GNU, // GNU Make installed as `gmake`
GNUStealthy, // GNU Make installed as `make`
Other, // Another make installed as `make`
}
fn isfile(path: &str) -> bool {
if let Ok(metadata) = std::fs::metadata(path) {
metadata.is_file()
impl Make {
fn command(self) -> &'static str {
if self == Make::GNU {
"gmake"
} else {
false
"make"
}
}
fn gnu(self) -> bool {
self != Make::Other
}
}
fn cwd() -> String {
match std::env::current_dir() {
Ok(pathbuf) => pathbuf.to_str().unwrap_or_else(|| panic!("cwd: cwd was not a valid utf8 string")).to_string(),
Err(err) => panic!("cwd: {}", err),
}
fn status(command: &mut std::process::Command) -> std::io::Result<std::process::ExitStatus> {
command
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
}
fn cd(path: &str) {
if let Err(err) = std::env::set_current_dir(path) {
panic!("cd: {}", err)
}
}
fn which_make() -> Option<Make> {
// check `gmake`
let result = status(std::process::Command::new("gmake").arg("-v"));
fn say(s: &str) {
println!("{}", s)
}
fn warn(s: &str) {
if let Err(err) = std::io::stderr().write(s.as_bytes()) {
panic!("warn: could not write to stderr: {}", err);
if let Ok(exit_status) = result {
if exit_status.success() {
return Some(Make::GNU);
}
if let Err(err) = std::io::stderr().write("\n".as_bytes()) {
panic!("warn: could not write to stderr: {}", err);
}
}
fn die(s: &str) {
warn(s);
std::process::exit(-1);
// check `make`. pass gmake specific flags to see if it's actually gmake
let result = status(std::process::Command::new("make").arg("-v").arg("--always-make"));
if let Ok(exit_status) = result {
return if exit_status.success() {
Some(Make::GNUStealthy)
} else {
Some(Make::Other)
};
}
return None;
}
fn main() {
let can_make = can("make");
let can_gmake = can("gmake");
if !(can_make || can_gmake) {
die("cannot find \"make\" or \"gmake\" in $PATH");
}
println!("can make: {}", can_make);
println!("can gmake: {}", can_gmake);
let make = match which_make() {
None => die!("Could not execute `make` or `gmake`."),
Some(make) => make,
};
loop {
if isfile("justfile") {
break;
}
if cwd() == "/" {
die("No justfile found.")
}
cd("..");
match std::fs::metadata("justfile") {
Ok(metadata) => if metadata.is_file() { break; },
Err(error) => die!("Error fetching justfile metadata: {}", error),
}
match std::env::current_dir() {
Ok(pathbuf) => if pathbuf.as_os_str() == "/" { die!("No justfile found"); },
Err(error) => die!("Error getting current dir: {}", error),
}
if let Err(error) = std::env::set_current_dir("..") {
die!("Error changing directory: {}", error);
}
}
let recipes: Vec<String> = std::env::args().skip(1).take_while(|arg| arg != "--").collect();
let arguments: Vec<String> = std::env::args().skip(1 + recipes.len() + 1).collect();
print!("{:?}", recipes);
print!("{:?}", arguments);
for (i, argument) in arguments.into_iter().enumerate() {
std::env::set_var(format!("ARG{}", i), argument);
}
// export as $ARG0 -> ARGX
// start at 0 or 1?
// exec $MAKE MAKEFLAGS='' --always-make --no-print-directory -f justfile ${RECIPES[*]}
let mut command = std::process::Command::new(make.command());
command.arg("MAKEFLAGS=");
if make.gnu() {
command.arg("--always-make").arg("--no-print-directory");
}
command.arg("-f").arg("justfile");
for recipe in recipes {
command.arg(recipe);
}
match command.status() {
Err(error) => die!("Failed to execute `{:?}`: {}", command, error),
Ok(exit_status) => match exit_status.code() {
Some(code) => std::process::exit(code),
None => std::process::exit(-1),
}
}
}