From 7a5190e7e1c3fd2edab1b53976c0c5a69f6823c9 Mon Sep 17 00:00:00 2001 From: Yuki Sawa Date: Tue, 21 Nov 2023 17:12:40 -0800 Subject: [PATCH] add rust as preset and refactor (#12) * merge * add rust as preset and refactor * add rust as preset and refactor * add rust as preset and refactor * readme --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 9 +- example/{ => python}/.cpa/flake8.cfg | 0 example/{ => python}/.cpa/prettier.json | 0 .../{ => python}/.github/workflows/ci.yaml | 0 example/{ => python}/.gitignore | 0 example/{ => python}/.pre-commit-config.yaml | 0 example/{ => python}/.vscode/extensions.json | 0 example/{ => python}/.vscode/settings.json | 0 example/{ => python}/Dockerfile | 0 example/{ => python}/Makefile | 0 example/{ => python}/main.py | 0 example/{ => python}/pyproject.toml | 5 +- example/rust/.cpa/prettier.json | 7 + example/rust/.github/workflows/ci.yaml | 86 ++++++++ example/rust/.gitignore | 189 ++++++++++++++++++ example/rust/.pre-commit-config.yaml | 70 +++++++ example/rust/.vscode/extensions.json | 9 + example/rust/.vscode/settings.json | 9 + example/rust/Cargo.toml | 8 + example/rust/Makefile | 25 +++ example/rust/rustfmt.toml | 7 + example/rust/src/main.rs | 3 + install.sh | 9 +- src/main.rs | 50 ++++- src/presets.rs | 142 +++++++++++++ src/python.rs | 121 ----------- templates/.pre-commit-config.yaml | 12 +- templates/{ => python}/Dockerfile | 0 templates/{ => python}/main.py | 0 templates/python/pyproject.toml | 62 ++++++ templates/rust/Cargo.toml | 8 + templates/rust/rustfmt.toml | 7 + templates/rust/src/main.rs | 3 + 35 files changed, 699 insertions(+), 146 deletions(-) rename example/{ => python}/.cpa/flake8.cfg (100%) rename example/{ => python}/.cpa/prettier.json (100%) rename example/{ => python}/.github/workflows/ci.yaml (100%) rename example/{ => python}/.gitignore (100%) rename example/{ => python}/.pre-commit-config.yaml (100%) rename example/{ => python}/.vscode/extensions.json (100%) rename example/{ => python}/.vscode/settings.json (100%) rename example/{ => python}/Dockerfile (100%) rename example/{ => python}/Makefile (100%) rename example/{ => python}/main.py (100%) rename example/{ => python}/pyproject.toml (89%) create mode 100644 example/rust/.cpa/prettier.json create mode 100644 example/rust/.github/workflows/ci.yaml create mode 100644 example/rust/.gitignore create mode 100644 example/rust/.pre-commit-config.yaml create mode 100644 example/rust/.vscode/extensions.json create mode 100644 example/rust/.vscode/settings.json create mode 100644 example/rust/Cargo.toml create mode 100644 example/rust/Makefile create mode 100644 example/rust/rustfmt.toml create mode 100644 example/rust/src/main.rs create mode 100644 src/presets.rs delete mode 100644 src/python.rs rename templates/{ => python}/Dockerfile (100%) rename templates/{ => python}/main.py (100%) create mode 100644 templates/python/pyproject.toml create mode 100644 templates/rust/Cargo.toml create mode 100644 templates/rust/rustfmt.toml create mode 100644 templates/rust/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 8472e48..d54ac0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,7 +166,7 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cpa" -version = "0.0.10" +version = "0.1.0" dependencies = [ "askama", "clap", diff --git a/Cargo.toml b/Cargo.toml index 205ee58..d4fc118 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cpa" -version = "0.0.10" +version = "0.1.0" edition = "2021" [dependencies] diff --git a/README.md b/README.md index c6feddb..89ddd70 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,7 @@ curl -sSL https://raw.githubusercontent.com/ysawa0/create-python-app/main/instal ```bash # cpa will be installed to ~/bin/cpa -# add ~/bin to your PATH -# eg: echo "export PATH=$PATH:~/bin" >> ~/.zshrc +# The installer will add ~/bin to your PATH ``` ### Windows @@ -43,18 +42,20 @@ cpa create --name myproject Optional params: -- `--preset`: Specifies a Python version for the project. Defaults to "python3.10" +- `--preset`: Specifies a preset for the project. Defaults to "python3.10". "pythonx.yz" and "rust" are supported. Example: ```bash cpa create --name myproject --preset python3.10 +cpa create --name myproject --preset rust ``` Update current working directory with CPA preset. ```bash cpa update --name myproject --preset python3.10 +cpa create --name myproject --preset rust ``` # Goals @@ -62,7 +63,7 @@ cpa update --name myproject --preset python3.10 - **Speed up Project Creation**: Reduce the time spent on repetitive setup tasks - **Best Practices**: Encourage best practices for code quality, formatting, and style by including configs for tools like `black`, `isort`, and `flake8`. - **Automation**: Automate tasks such as generating `.gitignore` files, setting up pre-commit hooks, and configuring code linters and formatters. -- Golang, Rust support planned +- Golang support planned # Contributions and Feedback diff --git a/example/.cpa/flake8.cfg b/example/python/.cpa/flake8.cfg similarity index 100% rename from example/.cpa/flake8.cfg rename to example/python/.cpa/flake8.cfg diff --git a/example/.cpa/prettier.json b/example/python/.cpa/prettier.json similarity index 100% rename from example/.cpa/prettier.json rename to example/python/.cpa/prettier.json diff --git a/example/.github/workflows/ci.yaml b/example/python/.github/workflows/ci.yaml similarity index 100% rename from example/.github/workflows/ci.yaml rename to example/python/.github/workflows/ci.yaml diff --git a/example/.gitignore b/example/python/.gitignore similarity index 100% rename from example/.gitignore rename to example/python/.gitignore diff --git a/example/.pre-commit-config.yaml b/example/python/.pre-commit-config.yaml similarity index 100% rename from example/.pre-commit-config.yaml rename to example/python/.pre-commit-config.yaml diff --git a/example/.vscode/extensions.json b/example/python/.vscode/extensions.json similarity index 100% rename from example/.vscode/extensions.json rename to example/python/.vscode/extensions.json diff --git a/example/.vscode/settings.json b/example/python/.vscode/settings.json similarity index 100% rename from example/.vscode/settings.json rename to example/python/.vscode/settings.json diff --git a/example/Dockerfile b/example/python/Dockerfile similarity index 100% rename from example/Dockerfile rename to example/python/Dockerfile diff --git a/example/Makefile b/example/python/Makefile similarity index 100% rename from example/Makefile rename to example/python/Makefile diff --git a/example/main.py b/example/python/main.py similarity index 100% rename from example/main.py rename to example/python/main.py diff --git a/example/pyproject.toml b/example/python/pyproject.toml similarity index 89% rename from example/pyproject.toml rename to example/python/pyproject.toml index cf3a018..13d0bdf 100644 --- a/example/pyproject.toml +++ b/example/python/pyproject.toml @@ -1,6 +1,5 @@ -# This file was configured by CPA. For additional details: https://github.com/ysawa0/create-python-app [tool.poetry] -name = "example" +name = "example/python" version = "0.0.1" description = "" authors = [ @@ -36,7 +35,7 @@ color = true [tool.isort] balanced_wrapping = true include_trailing_comma = true -known_first_party = "example" +known_first_party = "example/python" known_third_party = [ "boto3", # Common for AWS "django", # Common web framework, if used diff --git a/example/rust/.cpa/prettier.json b/example/rust/.cpa/prettier.json new file mode 100644 index 0000000..64f80e2 --- /dev/null +++ b/example/rust/.cpa/prettier.json @@ -0,0 +1,7 @@ +{ + "bracketSpacing": true, + "singleQuote": false, + "useTabs": false, + "tabWidth": 2, + "trailingComma": "all" +} diff --git a/example/rust/.github/workflows/ci.yaml b/example/rust/.github/workflows/ci.yaml new file mode 100644 index 0000000..660edd4 --- /dev/null +++ b/example/rust/.github/workflows/ci.yaml @@ -0,0 +1,86 @@ +name: CI + +on: + pull_request: # Start the job on all PRs + branches: [master, main] + types: [synchronize, opened, reopened, ready_for_review] + push: # Start the job on all main branch push + branches: [master, main] + +jobs: + precommit: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Set shfmt version environment variable + run: echo "SHFMT_VERSION=v3.7.0" >> $GITHUB_ENV + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip- + restore-keys: | + ${{ runner.os }}-pip- + + - name: Cache shfmt binary + uses: actions/cache@v3 + with: + path: /usr/local/bin/shfmt + key: ${{ runner.os }}-shfmt-${{ env.SHFMT_VERSION }} + restore-keys: | + ${{ runner.os }}-shfmt-${{ env.SHFMT_VERSION }} + ${{ runner.os }}-shfmt- + + - name: Cache Pre-Commit environments + uses: actions/cache@v3 + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pc-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: | + ${{ runner.os }}-pc-${{ hashFiles('.pre-commit-config.yaml') }} + ${{ runner.os }}-pc- + + - name: Install dependencies + run: | + python -m pip install pre-commit + pre-commit install + + - name: Install shfmt + run: | + SHFMT_VERSION=${{ env.SHFMT_VERSION }} + SHFMT_BIN="shfmt_${SHFMT_VERSION}_linux_amd64" + if [[ ! -f /usr/local/bin/shfmt ]]; then + wget -O shfmt "https://github.com/mvdan/sh/releases/download/${SHFMT_VERSION}/${SHFMT_BIN}" + chmod +x shfmt + sudo mv shfmt /usr/local/bin/ + fi + sudo apt-get install shellcheck + + - name: Run pre-commits + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + REPO_NAME=$(echo $GITHUB_REPOSITORY | sed 's/^.*\///') + DEFAULT_BRANCH=$(curl -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPOSITORY" | jq -r '.default_branch') + + git fetch + CUR_SHA=$(git log --pretty=tformat:"%H" -n1 . | tail -n1) + + echo "Default branch is $DEFAULT_BRANCH" + echo "Current SHA is $CUR_SHA" + + if [[ $GITHUB_REF == "refs/heads/$DEFAULT_BRANCH" ]]; then + pre-commit run --all + else + pre-commit run --from-ref origin/$DEFAULT_BRANCH --to-ref $CUR_SHA + fi diff --git a/example/rust/.gitignore b/example/rust/.gitignore new file mode 100644 index 0000000..ea1aea1 --- /dev/null +++ b/example/rust/.gitignore @@ -0,0 +1,189 @@ +########################################################################################## +# Python +# From: https://github.com/github/gitignore/blob/main/Python.gitignore +########################################################################################## +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +########################################################################################## +# Rust +# From: https://github.com/github/gitignore/blob/main/Rust.gitignore +########################################################################################## +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + + +########################################################################################## +# Misc +########################################################################################## +tmp* diff --git a/example/rust/.pre-commit-config.yaml b/example/rust/.pre-commit-config.yaml new file mode 100644 index 0000000..e60db58 --- /dev/null +++ b/example/rust/.pre-commit-config.yaml @@ -0,0 +1,70 @@ +default_language_version: + python: python3 + +repos: + ############################################################################# + # Misc + ############################################################################# + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-merge-conflict # Searches for merge conflict markers within files. + - id: check-added-large-files # Blocks commits that add large files. Default limit is 500kB. + # Can be configured with args, e.g., '--maxkb=1000' to change the limit. + # exclude: 'your_dir/.*' + # args: ['--maxkb=5000'] + - id: check-case-conflict # Identifies potential case-insensitive file name conflicts. + - id: check-ast # Validates the syntax of Python files. + - id: check-symlinks # Detects broken symlinks. + - id: trailing-whitespace # Removes any trailing whitespace at the end of lines. + - id: end-of-file-fixer # Ensures files end with a single newline or are empty. + + ############################################################################# + # JSON, TOML + ############################################################################# + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-json # Validates JSON files to ensure they are properly formatted and syntactically correct. + types: [json] + - id: check-toml # Checks TOML files for errors and format issues to ensure valid syntax. + types: [toml] + + ############################################################################# + # Shell + ############################################################################# + - repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: shfmt # Formats shell scripts to a standard convention using shfmt. + - id: shellcheck # Lints shell scripts to identify syntax and usage errors, with a specified severity of 'warning'. + args: + - --severity=warning + + ############################################################################# + # Rust + ############################################################################# + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt # Formats Rust code using rustfmt + - id: cargo-check # Checks Rust code for compilation errors and warnings + - id: clippy # Lints Rust code with clippy for common mistakes and style issues + + ############################################################################# + # CSS, Markdown, JavaScript, TypeScript, YAML style formatter + ############################################################################# + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.3 + hooks: + - id: prettier # An opinionated code formatter supporting multiple languages. + name: prettier + args: [--config, .cpa/prettier.json, --write] + types_or: + - css + - scss + - ts + - tsx + - javascript + - yaml + - markdown diff --git a/example/rust/.vscode/extensions.json b/example/rust/.vscode/extensions.json new file mode 100644 index 0000000..a73380a --- /dev/null +++ b/example/rust/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.isort", + "ms-python.black-formatter", + "ms-python.flake8" + ] +} diff --git a/example/rust/.vscode/settings.json b/example/rust/.vscode/settings.json new file mode 100644 index 0000000..bd6b16b --- /dev/null +++ b/example/rust/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "[python]": { + "editor.formatOnType": true, + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "flake8.args": ["--config=.cpa/flake8.cfg"], + "files.insertFinalNewline": true +} diff --git a/example/rust/Cargo.toml b/example/rust/Cargo.toml new file mode 100644 index 0000000..939581d --- /dev/null +++ b/example/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "example/rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/example/rust/Makefile b/example/rust/Makefile new file mode 100644 index 0000000..5c2a614 --- /dev/null +++ b/example/rust/Makefile @@ -0,0 +1,25 @@ +.PHONY: setuppc +setuppc: + @echo "Setting up pre-commit and hooks..." + python3 -m pip install pre-commit + pre-commit install + +ifeq ($(shell uname),Darwin) + @echo "Setting up shfmt (macOS)..." + brew install shfmt + + @echo "Setting up shellcheck (macOS)..." + brew install shellcheck +else + @echo "Setting up shfmt (Linux)..." + wget -qO shfmt "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_$(shell uname -m)" + chmod +x shfmt + sudo mv shfmt /usr/local/bin/shfmt + + @echo "Setting up shellcheck (Linux)..." + sudo apt-get install shellcheck || sudo yum install shellcheck || sudo dnf install shellcheck +endif + +.PHONY: reqtxt +reqtxt: + poetry export -f requirements.txt --output requirements.txt --without-hashes diff --git a/example/rust/rustfmt.toml b/example/rust/rustfmt.toml new file mode 100644 index 0000000..44cd578 --- /dev/null +++ b/example/rust/rustfmt.toml @@ -0,0 +1,7 @@ +max_width = 140 +# Below require nightly channel, run: rustup default nightly +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +imports_layout = "Horizontal" + +comment_width = 200 diff --git a/example/rust/src/main.rs b/example/rust/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/example/rust/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/install.sh b/install.sh index 765dfc6..1306ba1 100644 --- a/install.sh +++ b/install.sh @@ -58,9 +58,10 @@ chmod +x "$BINARY_PATH" || { rm "$ZIP_PATH" # # Optionally, append the target directory to PATH if it's not already there -# if [[ ":$PATH:" != *":$TARGET_DIR:"* ]]; then -# echo "export PATH=\$PATH:$TARGET_DIR" >>~/.bashrc -# echo "$TARGET_DIR added to PATH" -# fi +if [[ ":$PATH:" != *":$TARGET_DIR:"* ]]; then + echo "export PATH=\$PATH:$TARGET_DIR" >>~/.bashrc + echo "export PATH=\$PATH:$TARGET_DIR" >>~/.zshrc + echo "$TARGET_DIR added to PATH" +fi echo "cpa installed to $BINARY_PATH" diff --git a/src/main.rs b/src/main.rs index 33741ba..010df3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -mod python; +mod presets; use std::process; use clap::Parser; -use python::setup_preset; +use presets::{common, python, rust}; use regex::Regex; #[derive(Parser)] @@ -39,12 +39,30 @@ struct UpdateArgs { preset: String, } -fn check_pyver(preset: &str) { +pub struct Language { + language: String, + ver: String, +} + +#[allow(clippy::needless_return)] +fn validate_preset(preset: &str) -> Language { + if preset == "rust" { + return Language { + language: "rust".to_string(), + ver: "".to_string(), + }; + } + let re = Regex::new(r"python(3\.\d+|4\.\d+)").unwrap(); - if re.captures(preset).is_none() { + if let Some(caps) = re.captures(preset) { + return Language { + language: "python".to_string(), + ver: caps[1].to_string(), + }; + } else { eprintln!("Python version not recognized in --preset, invalid input. Expected format: 'python3.xx'"); process::exit(1); - }; + } } fn main() { @@ -52,9 +70,14 @@ fn main() { Cli::Create(args) => { println!("Creating project with name: {}", args.name); println!("Using preset: {:?} ", args.preset); - if args.preset.starts_with("python") { - check_pyver(&args.preset); - setup_preset(&args.preset, args.name, true); + let lang = validate_preset(&args.preset); + let create = true; + if lang.language == "python" { + let prefix = common(&args.name, create, &lang); + python(&args.name, &prefix, &lang); + } else if lang.language == "rust" { + let prefix = common(&args.name, create, &lang); + rust(&args.name, &prefix); } else { eprintln!("Preset: {:?} not supported", args.preset); } @@ -62,9 +85,14 @@ fn main() { Cli::Update(args) => { println!("Updating project with name: {}", args.name); println!("Using preset: {:?} ", args.preset); - if args.preset.starts_with("python") { - check_pyver(&args.preset); - setup_preset(&args.preset, args.name, false); + let lang = validate_preset(&args.preset); + let create = false; + if lang.language == "python" { + let prefix = common(&args.name, create, &lang); + python(&args.name, &prefix, &lang); + } else if lang.language == "rust" { + let prefix = common(&args.name, create, &lang); + rust(&args.name, &prefix); } else { eprintln!("Preset: {:?} not supported", args.preset); } diff --git a/src/presets.rs b/src/presets.rs new file mode 100644 index 0000000..2d3e0d8 --- /dev/null +++ b/src/presets.rs @@ -0,0 +1,142 @@ +extern crate regex; +use std::{ + fs::{self, File}, io::Write +}; + +use askama::Template; + +use crate::Language; + +trait CPATemplate { + fn write(&self, prefix: &str, path: &str); +} + +// Implement convenience trait for any type that implements `askama::Template` +impl CPATemplate for T { + fn write(&self, prefix: &str, path: &str) { + let mut content = self.render().expect("Failed to render file."); + content.push('\n'); + let mut f = File::create(format!("{}/{}", prefix, path)).expect("Could not create file"); + f.write_all(content.as_bytes()).expect("Could not write to file"); + } +} + +//////////////////////////////////// +// COMMON +//////////////////////////////////// +#[derive(Template)] +#[template(path = ".gitignore", escape = "none")] +pub struct GitIgnore {} + +#[derive(Template)] +#[template(path = ".vscode/settings.json", escape = "none")] +pub struct VSCodeSettings {} + +#[derive(Template)] +#[template(path = ".vscode/extensions.json", escape = "none")] +pub struct VSCodeExtensions {} + +#[derive(Template)] +#[template(path = "Makefile", escape = "none")] +pub struct Makefile {} + +#[derive(Template)] +#[template(path = ".github/workflows/ci.yaml", escape = "none")] +pub struct GhCI {} + +#[derive(Template)] +#[template(path = ".cpa/prettier.json", escape = "none")] +pub struct Prettier {} + +#[derive(Template)] +#[template(path = ".pre-commit-config.yaml", escape = "none")] +pub struct PreCommitConfig { + pub language: String, +} + +//////////////////////////////////// +// PYTHON +//////////////////////////////////// +#[derive(Template)] +#[template(path = "python/Dockerfile", escape = "none")] +pub struct PyDockerfile {} + +#[derive(Template)] +#[template(path = "python/main.py", escape = "none")] +pub struct PyMain {} + +#[derive(Template)] +#[template(path = "python/pyproject.toml", escape = "none")] +pub struct PyProject { + pub name: String, + pub python_ver: String, + pub black_target_ver: String, +} + +#[derive(Template)] +#[template(path = ".cpa/flake8.cfg", escape = "none")] +pub struct Flake8 {} + +//////////////////////////////////// +// RUST +//////////////////////////////////// +#[derive(Template)] +#[template(path = "rust/Cargo.toml", escape = "none")] +struct CargoToml { + name: String, +} + +#[derive(Template)] +#[template(path = "rust/rustfmt.toml", escape = "none")] +struct RustFmt {} + +#[derive(Template)] +#[template(path = "rust/src/main.rs", escape = "none")] +struct RustMain {} + +pub fn common(name: &str, create: bool, lang: &Language) -> String { + let prefix: String = if create { format!("./{}", name) } else { "./".to_string() }; + + // Create needed dirs + let _ = fs::create_dir_all(format!("{}/.cpa", prefix)); + let _ = fs::create_dir_all(format!("{}/.vscode", prefix)); + let _ = fs::create_dir_all(format!("{}/.github/workflows", prefix)); + + // Render common files + GhCI {}.write(&prefix, ".github/workflows/ci.yaml"); + GitIgnore {}.write(&prefix, ".gitignore"); + Makefile {}.write(&prefix, "Makefile"); + PreCommitConfig { + language: lang.language.to_string(), + } + .write(&prefix, ".pre-commit-config.yaml"); + Prettier {}.write(&prefix, ".cpa/prettier.json"); + VSCodeSettings {}.write(&prefix, ".vscode/settings.json"); + VSCodeExtensions {}.write(&prefix, ".vscode/extensions.json"); + prefix +} + +pub fn python(name: &str, prefix: &str, lang: &Language) { + let black_target_ver = format!("py{}", lang.ver.replace('.', "")); + + // Render Python-specific files + Flake8 {}.write(prefix, ".cpa/flake8.cfg"); + PyMain {}.write(prefix, "main.py"); + PyDockerfile {}.write(prefix, "Dockerfile"); + + let pyproj: PyProject = PyProject { + name: name.to_string(), + python_ver: lang.ver.to_string(), + black_target_ver, + }; + pyproj.write(prefix, "pyproject.toml"); +} + +pub fn rust(name: &str, prefix: &str) { + let _ = fs::create_dir_all(format!("{}/src", prefix)); + + // Render Python-specific files + RustMain {}.write(prefix, "src/main.rs"); + CargoToml { name: name.to_string() }.write(prefix, "Cargo.toml"); + RustFmt {}.write(prefix, "rustfmt.toml"); +} diff --git a/src/python.rs b/src/python.rs deleted file mode 100644 index 6bdfbdf..0000000 --- a/src/python.rs +++ /dev/null @@ -1,121 +0,0 @@ -extern crate regex; - -use std::{ - fs::{self, File}, io::Write, process -}; - -use askama::Template; -use regex::Regex; - -trait CPATemplate { - fn write(&self, prefix: &str, path: &str); -} - -// Implement convenience trait for any type that implements `askama::Template` -impl CPATemplate for T { - fn write(&self, prefix: &str, path: &str) { - let content = append_eof(self.render().expect("Failed to render file.")); - let mut f = File::create(format!("{}/{}", prefix, path)).expect("Could not create file"); - f.write_all(content.as_bytes()).expect("Could not write to file"); - } -} - -fn append_eof(mut s: String) -> String { - s.push('\n'); - s -} - -#[derive(Template)] -#[template(path = ".gitignore", escape = "none")] -struct GitIgnore {} - -#[derive(Template)] -#[template(path = ".vscode/settings.json", escape = "none")] -struct VSCodeSettings {} - -#[derive(Template)] -#[template(path = ".vscode/extensions.json", escape = "none")] -struct VSCodeExtensions {} - -#[derive(Template)] -#[template(path = "Makefile", escape = "none")] -struct Makefile {} - -#[derive(Template)] -#[template(path = "Dockerfile", escape = "none")] -struct Dockerfile {} - -#[derive(Template)] -#[template(path = "main.py", escape = "none")] -struct MainPy {} - -#[derive(Template)] -#[template(path = ".pre-commit-config.yaml", escape = "none")] -struct PreCommitConfig { - python: bool, -} - -#[derive(Template)] -#[template(path = "pyproject.toml", escape = "none")] -struct PyProject { - name: String, - python_ver: String, - black_target_ver: String, -} - -#[derive(Template)] -#[template(path = ".cpa/flake8.cfg", escape = "none")] -struct Flake8 {} - -#[derive(Template)] -#[template(path = ".cpa/prettier.json", escape = "none")] -struct Prettier {} - -#[derive(Template)] -#[template(path = ".github/workflows/ci.yaml", escape = "none")] -struct GHWorkflowCI {} - -pub fn setup_preset(mut preset: &str, name: String, create: bool) { - if preset == "python" { - preset = "python3.10"; - } - let mut prefix: String = "./".to_string(); - if create { - prefix = format!("./{}", name) - } - - // Create needed dirs - let _ = fs::create_dir_all(format!("{}/.cpa", prefix)); - let _ = fs::create_dir_all(format!("{}/.vscode", prefix)); - let _ = fs::create_dir_all(format!("{}/.github/workflows", prefix)); - - // Render files - GHWorkflowCI {}.write(&prefix, ".github/workflows/ci.yaml"); - VSCodeSettings {}.write(&prefix, ".vscode/settings.json"); - VSCodeExtensions {}.write(&prefix, ".vscode/extensions.json"); - GitIgnore {}.write(&prefix, ".gitignore"); - Makefile {}.write(&prefix, "Makefile"); - Dockerfile {}.write(&prefix, "Dockerfile"); - MainPy {}.write(&prefix, "main.py"); - PreCommitConfig { python: true }.write(&prefix, ".pre-commit-config.yaml"); - Flake8 {}.write(&prefix, ".cpa/flake8.cfg"); - Prettier {}.write(&prefix, ".cpa/prettier.json"); - - // Render Poetry conf - let re = Regex::new(r"python(3\.\d+|4\.\d+)").unwrap(); - let (python_ver, black_target_ver) = if let Some(caps) = re.captures(preset) { - let ver = caps[1].to_string(); - (ver.clone(), format!("py{}", ver.replace('.', ""))) - } else { - eprintln!("Python version not recognized in --preset, invalid input. Expected format: 'python3.xx'"); - process::exit(1); - }; - - let pyproj: PyProject = PyProject { - name: name.clone(), - python_ver, - black_target_ver, - }; - pyproj.write(&prefix, "pyproject.toml"); - println!("Project created at: {}", prefix) -} diff --git a/templates/.pre-commit-config.yaml b/templates/.pre-commit-config.yaml index 9df654f..1c05b4e 100644 --- a/templates/.pre-commit-config.yaml +++ b/templates/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: - id: shellcheck # Lints shell scripts to identify syntax and usage errors, with a specified severity of 'warning'. args: - --severity=warning -{% if python +%} +{% if language == "python" +%} ############################################################################# # Python ############################################################################# @@ -91,6 +91,16 @@ repos: # hooks: # - id: poetry-check # Makes sure the poetry configuration does not get committed in a broken state. # - id: poetry-lock # Ensures the poetry.lock file is up-to-date with the pyproject.toml changes. +{%+ endif %}{% if language == "rust" +%} + ############################################################################# + # Rust + ############################################################################# + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt # Formats Rust code using rustfmt + - id: cargo-check # Checks Rust code for compilation errors and warnings + - id: clippy # Lints Rust code with clippy for common mistakes and style issues {%+ endif %} ############################################################################# # CSS, Markdown, JavaScript, TypeScript, YAML style formatter diff --git a/templates/Dockerfile b/templates/python/Dockerfile similarity index 100% rename from templates/Dockerfile rename to templates/python/Dockerfile diff --git a/templates/main.py b/templates/python/main.py similarity index 100% rename from templates/main.py rename to templates/python/main.py diff --git a/templates/python/pyproject.toml b/templates/python/pyproject.toml new file mode 100644 index 0000000..25f3b68 --- /dev/null +++ b/templates/python/pyproject.toml @@ -0,0 +1,62 @@ +[tool.poetry] +name = "{{ name }}" +version = "0.0.1" +description = "" +authors = [ + "My Name " +] +license = "" + +[build-system] +requires = ["poetry-core>=1.7.0"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 120 +skip-string-normalization = false +target-version = ['{{ black_target_ver }}'] +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' +color = true + +[tool.isort] +balanced_wrapping = true +include_trailing_comma = true +known_first_party = "{{ name }}" +known_third_party = [ + "boto3", # Common for AWS + "django", # Common web framework, if used + "flask", # Common web framework, if used + "jinja2", # Common templating engine + "matplotlib", # Common for plotting + "numpy", # Common for numerical operations + "pandas", # Common for data manipulation + "pendulum", # Common for date time + "pytest", # Common for testing + "requests", # Common for HTTP requests + "sqlalchemy", # Common ORM for databases +] +multi_line_output = 3 +profile = "black" +line_length = 120 + +[tool.poetry.dependencies] +python = "^{{ python_ver }}" + +[tool.poetry.group.dev.dependencies] +pre-commit = ">=3.5.0" +pytest = "^7.3.1" +pytest-cov = "^4.1.0" diff --git a/templates/rust/Cargo.toml b/templates/rust/Cargo.toml new file mode 100644 index 0000000..6522548 --- /dev/null +++ b/templates/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "{{ name }}" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/templates/rust/rustfmt.toml b/templates/rust/rustfmt.toml new file mode 100644 index 0000000..44cd578 --- /dev/null +++ b/templates/rust/rustfmt.toml @@ -0,0 +1,7 @@ +max_width = 140 +# Below require nightly channel, run: rustup default nightly +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +imports_layout = "Horizontal" + +comment_width = 200 diff --git a/templates/rust/src/main.rs b/templates/rust/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/templates/rust/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}