diff --git a/.local/bin/projectdo b/.local/bin/projectdo index 4cc890d4..1ccc2504 100755 --- a/.local/bin/projectdo +++ b/.local/bin/projectdo @@ -23,38 +23,38 @@ QUIET=false DRY_RUN=false PROJECT_ROOT="${PROJECT_ROOT:-}" ACTION="" -TOOL_ARGS="" # Arguments to pass along to the tool +TOOL_ARGS="" # Arguments to pass along to the tool IS_TOOL=false # True if ACTION is 'tool' as this action is somewhat special. has_command() { - command -v "$1" >/dev/null 2>&1 + command -v "$1" >/dev/null 2>&1 } # When running the appropriate external tool this function is used which # evaluates the given command while respecting $QUIET and $DRY_RUN. execute() { - if [ $QUIET = false ]; then - echo "$1" $TOOL_ARGS - fi - if [ $DRY_RUN = false ]; then - if [ $QUIET = false ]; then - eval "$1" $TOOL_ARGS - else - eval "$1" $TOOL_ARGS > /dev/null - fi - fi + if [ $QUIET = false ]; then + echo "$1" $TOOL_ARGS + fi + if [ $DRY_RUN = false ]; then + if [ $QUIET = false ]; then + eval "$1" $TOOL_ARGS + else + eval "$1" $TOOL_ARGS >/dev/null + fi + fi } # Takes the name of a tool and an action. If the action is `tool` it is # ignored. It handles the common case where the tool is invoked as `tool # action`. execute_command() { - if [ "$2" = tool ]; then - execute "$1" - else - execute "$1 $2" - fi - exit 0 + if [ "$2" = tool ]; then + execute "$1" + else + execute "$1 $2" + fi + exit 0 } # Every supported tool requires a function `try_tool` where `tool` is a name @@ -71,241 +71,247 @@ execute_command() { # JavaScript + NodeJS try_nodejs() { - if [ ! -f package.json ]; then - return 1 - fi - if [ -f yarn.lock ]; then - tool=yarn - else - tool=npm - fi - if ! has_command $tool; then - echo "Found a package.json file but '$tool' is not installed." - exit 1 - fi - local node_action="$ACTION" - # Only the "run" action need translation, the others match 1-to-1 - if [ "$ACTION" = run ]; then - node_action=start - fi - # We check if the package.json file contains an appropriate script. We use - # grep for this. The check is not 100% bulletproof, but it's very close. We - # could've used `npm run` to get the authorative list of the scripts but - # invoking `npm` is two orders of magnitude slower which leads to a - # noticeable delay. - if ! $IS_TOOL && ! grep -q "^[[:space:]]*\"${node_action}\":" package.json; then - return 0 - fi - execute_command "$tool" "$node_action" + if [ ! -f package.json ]; then + return 1 + fi + if [ -f yarn.lock ]; then + tool=yarn + else + tool=npm + fi + if ! has_command $tool; then + echo "Found a package.json file but '$tool' is not installed." + exit 1 + fi + local node_action="$ACTION" + # Only the "run" action need translation, the others match 1-to-1 + # if [ "$ACTION" = run ]; then + # node_action=start + # fi + # We check if the package.json file contains an appropriate script. We use + # grep for this. The check is not 100% bulletproof, but it's very close. We + # could've used `npm run` to get the authorative list of the scripts but + # invoking `npm` is two orders of magnitude slower which leads to a + # noticeable delay. + # if ! $IS_TOOL && ! grep -q "^[[:space:]]*\"${node_action}\":" package.json; then + # return 0 + # fi + execute_command "$tool" "$node_action" } # Rust + Cargo try_cargo() { - if [ -f Cargo.toml ]; then - execute_command cargo "$ACTION" - fi + if [ -f Cargo.toml ]; then + execute_command cargo "$ACTION" + fi } # CMake try_cmake() { - if [ -f CMakeLists.txt ] && [ "$ACTION" = test ]; then - if [ -f build ]; then - execute "cmake --build build --target test" - else - execute "cmake --build . --target test" - fi - exit - fi + if [ -f CMakeLists.txt ] && [ "$ACTION" = test ]; then + if [ -f build ]; then + execute "cmake --build build --target test" + else + execute "cmake --build . --target test" + fi + exit + fi } # Haskell + Stack try_stack() { - if [ -f package.yaml ] && [ -f stack.yaml ]; then - execute_command stack "$ACTION" - fi + if [ -f package.yaml ] && [ -f stack.yaml ]; then + execute_command stack "$ACTION" + fi } # Haskell + Cabal try_cabal() { - cabal_file="$(find ./ -maxdepth 1 -name "*.cabal" 2> /dev/null | wc -l)" - if [ "$cabal_file" -gt 0 ] && [ ! -f stack.yml ]; then - execute_command cabal "$ACTION" - fi + cabal_file="$(find ./ -maxdepth 1 -name "*.cabal" 2>/dev/null | wc -l)" + if [ "$cabal_file" -gt 0 ] && [ ! -f stack.yml ]; then + execute_command cabal "$ACTION" + fi } # Maven try_maven() { - if [ -f pom.xml ]; then - case $ACTION in - build) - execute "mvn compile" - exit ;; - test) - execute "mvn test" - exit ;; - run) - echo "projectdo does not know how to run a project with Maven." - exit - esac - fi + if [ -f pom.xml ]; then + case $ACTION in + build) + execute "mvn compile" + exit + ;; + test) + execute "mvn test" + exit + ;; + run) + echo "projectdo does not know how to run a project with Maven." + exit + ;; + esac + fi } # Clojure + Leiningen try_lein() { - if [ -f project.clj ] && [ "$ACTION" = test ]; then - execute "lein test" - fi + if [ -f project.clj ] && [ "$ACTION" = test ]; then + execute "lein test" + fi } # Makefile has_make_target() { - target="${1?}" - output=$(make -n "${target}" 2>&1) - exit_code=$? - if [ $exit_code -ne 0 ]; then - return $exit_code - fi + target="${1?}" + output=$(make -n "${target}" 2>&1) + exit_code=$? + if [ $exit_code -ne 0 ]; then + return $exit_code + fi - # If there is a file with the name of the target we're looking for but no - # actual target with that name, make will exit successfully with that - # message. We need to consider that case as a "target not found". Note that - # the way the target is quoted in the output (`test' vs 'test') can differ - # across OSes so we only check a prefix up to the problematic quotes. - if expr "$output" : "make: Nothing to be done for" 1> /dev/null; then - return 1 - fi + # If there is a file with the name of the target we're looking for but no + # actual target with that name, make will exit successfully with that + # message. We need to consider that case as a "target not found". Note that + # the way the target is quoted in the output (`test' vs 'test') can differ + # across OSes so we only check a prefix up to the problematic quotes. + if expr "$output" : "make: Nothing to be done for" 1>/dev/null; then + return 1 + fi - return 0 + return 0 } try_makefile() { - if [ -f Makefile ]; then - if ! has_command "make"; then - echo "Found a Makefile but 'make' is not installed." - exit 1 - fi - if $IS_TOOL || [ "$ACTION" = build ]; then - # For make "build" is the default action when running `make` - execute "make" - exit - elif [ "$ACTION" = test ]; then - # Let's see if the makefile contains a test or check target - if has_make_target "test"; then - execute "make test" - exit - elif has_make_target "check"; then - execute "make check" - exit - fi - fi - fi - return 1 + if [ -f Makefile ]; then + if ! has_command "make"; then + echo "Found a Makefile but 'make' is not installed." + exit 1 + fi + if $IS_TOOL || [ "$ACTION" = build ]; then + # For make "build" is the default action when running `make` + execute "make" + exit + elif [ "$ACTION" = test ]; then + # Let's see if the makefile contains a test or check target + if has_make_target "test"; then + execute "make test" + exit + elif has_make_target "check"; then + execute "make check" + exit + fi + fi + fi + return 1 } # Python try_python() { - if [ -f pyproject.toml ]; then - if grep -q -m 1 "^\[tool.poetry\]$" pyproject.toml; then - case $ACTION in - build) - execute "poetry build" - exit ;; - test) - # TODO: There are other Python test frameworks, it would be nice to - # detect and run the right one. - execute "poetry run pytest" - exit ;; - run) - echo "projectdo does not know how to run a project with Poetry." - exit - esac - else - echo "Found a pyproject.toml file but projectdo is not sure how to run it." - exit - fi - return 1 - fi + if [ -f pyproject.toml ]; then + if grep -q -m 1 "^\[tool.poetry\]$" pyproject.toml; then + case $ACTION in + build) + execute "poetry build" + exit + ;; + test) + # TODO: There are other Python test frameworks, it would be nice to + # detect and run the right one. + execute "poetry run pytest" + exit + ;; + run) + echo "projectdo does not know how to run a project with Poetry." + exit + ;; + esac + else + echo "Found a pyproject.toml file but projectdo is not sure how to run it." + exit + fi + return 1 + fi } # Go try_go() { - if [ -f go.mod ] && [ "$ACTION" = test ]; then - # We detect Makefiles before we detect Go, so here we know that the Go - # project is _not_ tested by a Makefile. + if [ -f go.mod ] && [ "$ACTION" = test ]; then + # We detect Makefiles before we detect Go, so here we know that the Go + # project is _not_ tested by a Makefile. - # Check if the project uses Mage. A magefile could in theory have any name, - # but `magefile.go` seems to be the convention. - if grep -q -m 1 '^func Check(' magefile.go; then - execute "mage check" - exit - elif grep -q -m 1 '^func Test(' magefile.go; then - execute "mage test" - exit - fi - execute "go test" - exit - fi + # Check if the project uses Mage. A magefile could in theory have any name, + # but `magefile.go` seems to be the convention. + if grep -q -m 1 '^func Check(' magefile.go; then + execute "mage check" + exit + elif grep -q -m 1 '^func Test(' magefile.go; then + execute "mage test" + exit + fi + execute "go test" + exit + fi } # LaTeX try_latex() { - if [ -f Tectonic.toml ] && [ "$ACTION" = build ]; then - execute "tectonic -X build" - exit - fi + if [ -f Tectonic.toml ] && [ "$ACTION" = build ]; then + execute "tectonic -X build" + exit + fi } # End of list of tools detect_and_run() { - try_nodejs - try_cargo - try_stack - try_cabal - try_cmake - try_maven - try_lein - try_makefile - try_python - try_go - try_latex + try_nodejs + try_cargo + try_stack + try_cabal + try_cmake + try_maven + try_lein + try_makefile + try_python + try_go + try_latex } set_project_root() { - if [ -n "$PROJECT_ROOT" ]; then - return - fi + if [ -n "$PROJECT_ROOT" ]; then + return + fi - if has_command git; then - # Find the root of the git repository if we're inside one. If we're in a - # git submodule then the root of the outer git repo is used. If we're not - # in a git repo the git command will not output anything and $PROJECT_ROOT - # is set to the empty string which is fine. - PROJECT_ROOT=$(git rev-parse --show-superproject-working-tree --show-toplevel 2> /dev/null | head -n 1) - fi + if has_command git; then + # Find the root of the git repository if we're inside one. If we're in a + # git submodule then the root of the outer git repo is used. If we're not + # in a git repo the git command will not output anything and $PROJECT_ROOT + # is set to the empty string which is fine. + PROJECT_ROOT=$(git rev-parse --show-superproject-working-tree --show-toplevel 2>/dev/null | head -n 1) + fi } nothing_found() { - echo "No way to $ACTION found :'(" - exit 1 + echo "No way to $ACTION found :'(" + exit 1 } display_version() { - echo "$(basename "$0") version $VERSION" + echo "$(basename "$0") version $VERSION" } display_help() { - echo "Usage: $(basename "$0") [options] [action] [tool-arguments] + echo "Usage: $(basename "$0") [options] [action] [tool-arguments] Options: -h, --help Display this help. -n, -d, --dry-run Do not execute any commands with side-effects. @@ -322,64 +328,77 @@ Tool arguments: # Main execution starts here -while getopts dhnqv-: c -do - case $c in - d) DRY_RUN=true ;; - h) display_help; exit 0 ;; - n) DRY_RUN=true ;; - q) QUIET=true ;; - v) display_version; exit 0 ;; - -) case $OPTARG in - help) display_help; exit 0 ;; - dry-run) DRY_RUN=true ;; - quiet) QUIET=true ;; - version) display_version; exit 0 ;; - '' ) break ;; # "--" should terminate argument processing - * ) echo "Illegal option --$OPTARG" >&2; exit 1 ;; - esac ;; - \?) exit 1 ;; - esac +while getopts dhnqv-: c; do + case $c in + d) DRY_RUN=true ;; + h) + display_help + exit 0 + ;; + n) DRY_RUN=true ;; + q) QUIET=true ;; + v) + display_version + exit 0 + ;; + -) case $OPTARG in + help) + display_help + exit 0 + ;; + dry-run) DRY_RUN=true ;; + quiet) QUIET=true ;; + version) + display_version + exit 0 + ;; + '') break ;; # "--" should terminate argument processing + *) + echo "Illegal option --$OPTARG" >&2 + exit 1 + ;; + esac ;; + \?) exit 1 ;; + esac done -shift $((OPTIND-1)) # Shift away the parsed option arguments +shift $((OPTIND - 1)) # Shift away the parsed option arguments if [ "$1" = test ] || - [ "$1" = run ] || - [ "$1" = build ] || - [ "$1" = tool ]; then - ACTION=$1 - if [ "$1" = tool ]; then - IS_TOOL=true - fi - shift 1 # Remove the action from the arguments - TOOL_ARGS=$@ + [ "$1" = run ] || + [ "$1" = build ] || + [ "$1" = tool ]; then + ACTION=$1 + if [ "$1" = tool ]; then + IS_TOOL=true + fi + shift 1 # Remove the action from the arguments + TOOL_ARGS=$@ else - if [ -z "$1" ]; then - echo "No action specified." - else - echo "$1 is not a valid action." - fi - echo "" - echo "The valid actions are: build, run, test, tool" - echo "" - echo "Example: projectdo test" - exit 1 + if [ -z "$1" ]; then + echo "No action specified." + else + echo "$1 is not a valid action." + fi + echo "" + echo "The valid actions are: build, run, test, tool" + echo "" + echo "Example: projectdo test" + exit 1 fi set_project_root -while : -do - # We don't want to do anything if we are in the home or root directory - if [ "$PWD" = "$HOME" ] || [ "$PWD" = / ]; then - nothing_found - fi - detect_and_run - # If we didn't detect a tool to run in this directory we go up one directory - # while ensuring that we don't leave the project root - if [ "$PWD" = "$PROJECT_ROOT" ]; then - nothing_found - fi - cd .. +while :; do + # We don't want to do anything if we are in the home or root directory + if [ "$PWD" = "$HOME" ] || [ "$PWD" = / ]; then + nothing_found + fi + detect_and_run + # If we didn't detect a tool to run in this directory we go up one directory + # while ensuring that we don't leave the project root + if [ "$PWD" = "$PROJECT_ROOT" ]; then + nothing_found + fi + cd .. done