about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.envrc11
-rw-r--r--.gitignore15
-rw-r--r--.reuse/templates/default.jinja226
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml25
-rw-r--r--README.md23
-rw-r--r--cog.toml34
-rw-r--r--docs/quotify.1.md57
-rw-r--r--flake.lock139
-rw-r--r--flake.nix134
-rwxr-xr-xscripts/optimize_images.sh133
-rw-r--r--src/main.rs39
-rw-r--r--treefmt.nix79
-rwxr-xr-xupdate.sh15
14 files changed, 746 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..c8c5665
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,11 @@
+use flake || use nix
+watch_file flake.nix
+
+PATH_add ./target/debug
+PATH_add ./target/release
+PATH_add ./scripts
+
+if on_git_branch; then
+  echo && git status --short --branch &&
+  echo && git fetch --verbose
+fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a2dcaa2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This file is part of Quotify - A simple CLI utility to shell quote the text
+# inputted into it.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+# build
+/target
+/result
+
+# dev env
+.direnv
diff --git a/.reuse/templates/default.jinja2 b/.reuse/templates/default.jinja2
new file mode 100644
index 0000000..10c960a
--- /dev/null
+++ b/.reuse/templates/default.jinja2
@@ -0,0 +1,26 @@
+{#
+Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+SPDX-License-Identifier: AGPL-3.0-or-later
+
+This file is part of Quotify - A simple CLI utility to shell quote the text
+inputted into it.
+
+You should have received a copy of the License along with this program.
+If not, see <https://www.gnu.org/licenses/agpl.txt>.
+#}
+
+{% for copyright_line in copyright_lines %}
+{{ copyright_line }}
+{% endfor %}
+{% for contributor_line in contributor_lines %}
+SPDX-FileContributor: {{ contributor_line }}
+{% endfor %}
+{% for expression in spdx_expressions %}
+SPDX-License-Identifier: {{ expression }}
+{% endfor %}
+
+This file is part of Quotify - A simple CLI utility to shell quote the text
+inputted into it.
+
+You should have received a copy of the License along with this program.
+If not, see <https://www.gnu.org/licenses/agpl.txt>.
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..0ee492e
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
+
+[[package]]
+name = "quotify"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..724f9e5
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,25 @@
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This file is part of Quotify - A simple CLI utility to shell quote the text
+# inputted into it.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+[package]
+name = "quotify"
+description = "A simple cli utility to shell quote the text inputed into it"
+version = "0.1.0"
+edition = "2021"
+license = "%INIT_SPDX_LICENSE_IDENTIFER"
+homepage = "%INIT_APPLICATION_HOMEPAGE"
+repository = "https://git.vhack.eu/soispha/tools/quotify"
+# TODO
+# categories = [""]
+# keywords = ["", ""]
+
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[dependencies]
+anyhow = "1.0.89"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..31c2a85
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+<!--
+Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+SPDX-License-Identifier: AGPL-3.0-or-later
+
+This file is part of Quotify - A simple CLI utility to shell quote the text
+inputted into it.
+
+You should have received a copy of the License along with this program.
+If not, see <https://www.gnu.org/licenses/agpl.txt>.
+-->
+
+# Quotify
+
+> A simple cli utility to shell quote the text inputed into it
+
+## Licensing
+
+This project complies with the REUSE v3.2 specification. This means that every file
+clearly states its copyright.
+Please run `./scripts/cprh.sh contributer NAME EMAIL FILES..` after you
+contributed to `FILES..` to record your contribution (obviously replacing
+the `NAME`, `EMAIL` and `FILES..` placeholders with your name, email, and
+the paths to the changed files respectively (see the `--help` output for more)).
diff --git a/cog.toml b/cog.toml
new file mode 100644
index 0000000..3c6df32
--- /dev/null
+++ b/cog.toml
@@ -0,0 +1,34 @@
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This file is part of Quotify - A simple CLI utility to shell quote the text
+# inputted into it.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+tag_prefix = "v"
+branch_whitelist = ["main", "prime"]
+ignore_merge_commits = false
+
+pre_bump_hooks = [
+  "reuse lint",                    # Check licensing status.
+  "nix flake check",               # Verify the project builds.
+  "cargo set-version {{version}}", # Bump version in Cargo.toml.
+  "nix fmt",                       # Format.
+]
+post_bump_hooks = [
+  "git push",
+  "cargo publish",
+  "git push origin v{{version}}", # push the new tag to origin
+]
+
+[bump_profiles]
+
+[changelog]
+path = "NEWS.md"
+template = "remote"
+remote = "git.vhack.eu"
+repository = "tools/quotify"
+owner = "soispha"
+authors = [{ signature = "Benedikt Peetz", username = "soispha" }]
diff --git a/docs/quotify.1.md b/docs/quotify.1.md
new file mode 100644
index 0000000..546b559
--- /dev/null
+++ b/docs/quotify.1.md
@@ -0,0 +1,57 @@
+<!--
+Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+SPDX-License-Identifier: AGPL-3.0-or-later
+
+This file is part of Quotify - A simple CLI utility to shell quote the text
+inputted into it.
+
+You should have received a copy of the License along with this program.
+If not, see <https://www.gnu.org/licenses/agpl.txt>.
+-->
+
+% QUOTIFY(1) quotify 0.1.0
+% Benedikt Peetz
+% Oct 2024
+
+# NAME
+
+quotify - A simple cli utility to shell quote the text inputed into it
+
+# SYNOPSIS
+
+**quotify** \[*--help*|*--version*\]
+
+# DESCRIPTION
+
+TODO
+
+# OPTIONS
+
+**--help**, **-h**
+: Displays a help message and exit.
+
+**--version**, **-v**
+: Displays the software version and exit.
+
+# EXAMPLES
+
+**quotify**
+: TODO. See the Description section for further details.
+
+# FILES
+
+*name.file*
+: This file is important because it does x.
+
+# BUGS
+
+Report bugs to <mailto://soispha@vhack.eu>.
+
+# COPYRIGHT
+
+Copyright (C) 2024  Benedikt Peetz
+
+This program is free software.
+
+You should have received a copy of the License
+along with this program.  If not, see <https://www.gnu.org/licenses/agpl.txt>.
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..b491dc4
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,139 @@
+{
+  "nodes": {
+    "crane": {
+      "locked": {
+        "lastModified": 1727974419,
+        "narHash": "sha256-WD0//20h+2/yPGkO88d2nYbb23WMWYvnRyDQ9Dx4UHg=",
+        "owner": "ipetkov",
+        "repo": "crane",
+        "rev": "37e4f9f0976cb9281cd3f0c70081e5e0ecaee93f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "ipetkov",
+        "repo": "crane",
+        "type": "github"
+      }
+    },
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1696426674,
+        "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-utils": {
+      "inputs": {
+        "systems": [
+          "systems"
+        ]
+      },
+      "locked": {
+        "lastModified": 1726560853,
+        "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1728031656,
+        "narHash": "sha256-JXumn7X+suKEcehp4rchSvBzIboqyybQ5bLK4wk7gQU=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "eeeb90a1dd3c9bea3afdbc76fd34d0fb2a727c7a",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "crane": "crane",
+        "flake-compat": "flake-compat",
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs",
+        "rust-overlay": "rust-overlay",
+        "systems": "systems",
+        "treefmt-nix": "treefmt-nix"
+      }
+    },
+    "rust-overlay": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1728008962,
+        "narHash": "sha256-MjGMCVKqafsrqLQYJHHKXJkvocTjkxKjadBfN952/Zw=",
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "rev": "862d0c1e5fe2348a22044f225afef39b75df8cf0",
+        "type": "github"
+      },
+      "original": {
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "type": "github"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1680978846,
+        "narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=",
+        "owner": "nix-systems",
+        "repo": "x86_64-linux",
+        "rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "x86_64-linux",
+        "type": "github"
+      }
+    },
+    "treefmt-nix": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1727984844,
+        "narHash": "sha256-xpRqITAoD8rHlXQafYZOLvUXCF6cnZkPfoq67ThN0Hc=",
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "rev": "4446c7a6fc0775df028c5a3f6727945ba8400e64",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..db88011
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,134 @@
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This file is part of Quotify - A simple CLI utility to shell quote the text
+# inputted into it.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+{
+  description = "A simple cli utility to shell quote the text inputed into it";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+
+    treefmt-nix = {
+      url = "github:numtide/treefmt-nix";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+      };
+    };
+
+    crane = {
+      url = "github:ipetkov/crane";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+      };
+    };
+    rust-overlay = {
+      url = "github:oxalica/rust-overlay";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+        flake-utils.follows = "flake-utils";
+      };
+    };
+
+    # inputs for following
+    systems = {
+      url = "github:nix-systems/x86_64-linux"; # only evaluate for this system
+    };
+    flake-compat = {
+      url = "github:edolstra/flake-compat";
+      flake = false;
+    };
+    flake-utils = {
+      url = "github:numtide/flake-utils";
+      inputs = {
+        systems.follows = "systems";
+      };
+    };
+  };
+
+  outputs = {
+    self,
+    nixpkgs,
+    flake-utils,
+    treefmt-nix,
+    crane,
+    rust-overlay,
+    ...
+  }:
+    flake-utils.lib.eachDefaultSystem (system: let
+      pkgs = import nixpkgs {
+        inherit system;
+        overlays = [(import rust-overlay)];
+      };
+
+      nightly = false;
+      rust_minimal =
+        if nightly
+        then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal)
+        else pkgs.rust-bin.stable.latest.minimal;
+      rust_default =
+        if nightly
+        then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)
+        else pkgs.rust-bin.stable.latest.default;
+
+      cargo_toml = craneLib.cleanCargoToml {cargoToml = ./Cargo.toml;};
+      pname = cargo_toml.package.name;
+
+      craneLib = (crane.mkLib pkgs).overrideToolchain rust_minimal;
+      craneBuild = craneLib.buildPackage {
+        src = craneLib.cleanCargoSource ./.;
+
+        doCheck = true;
+      };
+
+      manual = pkgs.stdenv.mkDerivation {
+        name = "${pname}-manual";
+        inherit (cargo_toml.package) version;
+
+        src = ./docs;
+        nativeBuildInputs = with pkgs; [pandoc];
+
+        buildPhase = ''
+          mkdir --parents $out/docs;
+
+          pandoc "./${pname}.1.md" -s -t man > $out/docs/${pname}.1
+        '';
+
+        installPhase = ''
+          install -D $out/docs/${pname}.1  $out/share/man/man1/${pname};
+        '';
+      };
+
+      treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;};
+    in {
+      packages.default = pkgs.symlinkJoin {
+        inherit (cargo_toml.package) name;
+
+        paths = [manual craneBuild];
+      };
+
+      checks = {
+        inherit craneBuild;
+        formatting = treefmtEval.config.build.check self;
+      };
+
+      formatter = treefmtEval.config.build.wrapper;
+
+      devShells.default = pkgs.mkShell {
+        packages = with pkgs; [
+          cocogitto
+
+          rust_default
+          cargo-edit
+
+          reuse
+        ];
+      };
+    });
+}
+# vim: ts=2
+
diff --git a/scripts/optimize_images.sh b/scripts/optimize_images.sh
new file mode 100755
index 0000000..75f04af
--- /dev/null
+++ b/scripts/optimize_images.sh
@@ -0,0 +1,133 @@
+#!/usr/bin/env nix
+#! nix shell nixpkgs#optipng nixpkgs#jpegoptim nixpkgs#nodePackages.svgo nixpkgs#dash --command dash
+
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This file is part of Quotify - A simple CLI utility to shell quote the text
+# inputted into it.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+# shellcheck shell=dash
+
+# source: https://github.com/stride-tasks/stride/blob/148d513297c8ae66d79fc287769adfe5e711c93c/scripts/optimize-images
+
+PROJECT_DIR="$(git rev-parse --show-toplevel)"
+
+cd "$PROJECT_DIR" || {
+    echo "No '$PROJECT_DIR' ?!"
+    exit 1
+}
+
+PNG_OPITMIZE_COMMAND='optipng --quiet -o7 -preserve'
+JPG_OPITMIZE_COMMAND='jpegoptim --quiet --strip-all -m76'
+SVG_OPITMIZE_COMMAND='svgo --multipass --quiet --input'
+
+# The extension is the part after the first dot in the filename:
+#
+# For example:
+# file.png    => png
+# file.tar.gz => tar.gz
+find_files_by_extension() {
+    wanted_extension="$1"
+    tmp="$(mktemp)"
+
+    git ls-files --cached --modified --other --exclude-standard --deduplicate | while IFS= read -r file; do
+        extension="${file#*.}"
+        if [ "$extension" = "$wanted_extension" ]; then
+            echo "$file"
+        else
+            :
+            # echo "'$file' with extension: '$extension' does not match filter: '$wanted_extension'" 1>&2
+        fi
+    done >"$tmp"
+
+    echo "$tmp"
+}
+
+size_of() {
+    du -b "$1" | cut -f1
+}
+
+bytes_human() {
+    number="$1"
+
+    numfmt --to=iec-i --suffix=B --format="%9.2f" "$number"
+}
+
+# https://stackoverflow.com/questions/44695878/how-to-calculate-percentage-in-shell-script
+# Native POSIX solution using string manipulation (assumes integer inputs).
+percent() {
+    DP="$1"
+    SDC="$2"
+
+    # Special case when DP is zero.
+    [ "$DP" = "0" ] && echo "0.00" && return
+
+    #                                  # e.g. round down   e.g. round up
+    #                                  # DP=1 SDC=3        DP=2 SDC=3
+    Percent=$((DP * 100000 / SDC + 5)) # Percent=33338     Percent=66671
+    Whole=${Percent%???}               # Whole=33          Whole=66
+    Percent=${Percent#"$Whole"}        # Percent=338       Percent=671
+    Percent=$Whole.${Percent%?}        # Percent=33.33     Percent=66.67
+    echo "$Percent"
+}
+
+TOTAL=0
+TOTAL_SAVED=0
+
+optimize_files() {
+    FILTER="$1"
+    PROGRAM="$2"
+
+    printf "%s" "Processing $FILTER files:"
+
+    FILES="$(find_files_by_extension "$FILTER")"
+    COUNT=$(wc -l <"$FILES")
+
+    if [ "$COUNT" -eq 0 ]; then
+        echo " no files found!"
+        return
+    fi
+
+    echo ""
+
+    I=1
+
+    TOTAL_INNER=0
+    TOTAL_SAVED_INNER=0
+
+    while IFS= read -r f; do
+        printf "%-2s/${COUNT} $f ... " "$I"
+
+        SIZE="$(size_of "$f")"
+
+        $PROGRAM "$f"
+
+        NEW_SIZE="$(size_of "$f")"
+        DIFF=$((SIZE - NEW_SIZE))
+
+        echo "saved: $(bytes_human "$DIFF") ($(percent $DIFF "$SIZE")%)"
+
+        TOTAL_INNER=$((TOTAL_INNER + SIZE))
+        TOTAL_SAVED_INNER=$((TOTAL_SAVED_INNER + DIFF))
+
+        I=$((I + 1))
+    done <"$FILES"
+    rm "$FILES"
+
+    echo "Total saved for $FILTER: $(bytes_human "$TOTAL_SAVED_INNER") ($(percent $TOTAL_SAVED_INNER $TOTAL_INNER)%)"
+    echo ""
+
+    TOTAL=$((TOTAL + TOTAL_INNER))
+    TOTAL_SAVED=$((TOTAL_SAVED + TOTAL_SAVED_INNER))
+}
+
+optimize_files 'png' "$PNG_OPITMIZE_COMMAND"
+optimize_files 'jpg' "$JPG_OPITMIZE_COMMAND"
+optimize_files 'jpeg' "$JPG_OPITMIZE_COMMAND"
+optimize_files 'svg' "$SVG_OPITMIZE_COMMAND"
+
+echo "Total saved: $(bytes_human "$TOTAL_SAVED") ($(percent $TOTAL_SAVED $TOTAL)%)"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..a6b047b
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,39 @@
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This file is part of Quotify - A simple CLI utility to shell quote the text
+// inputted into it.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+use std::{
+    env::args,
+    io::{stdin, Read},
+};
+
+use anyhow::{Context, Result};
+
+fn main() -> Result<()> {
+    let text: String = {
+        if args().count() != 1 {
+            args().skip(1).collect()
+        } else {
+            let mut stdin = stdin();
+            let mut buf = vec![];
+            stdin
+                .read_to_end(&mut buf)
+                .context("Failed to read stdin")?;
+
+            let output =
+                String::from_utf8(buf).context("Failed to decode stdin as a utf8 string")?;
+            output
+        }
+    };
+
+    let quoted_text = text.replace('\'', "'\\''");
+
+    print!("'{}'", quoted_text);
+
+    Ok(())
+}
diff --git a/treefmt.nix b/treefmt.nix
new file mode 100644
index 0000000..0015102
--- /dev/null
+++ b/treefmt.nix
@@ -0,0 +1,79 @@
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This file is part of Quotify - A simple CLI utility to shell quote the text
+# inputted into it.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+{
+  treefmt-nix,
+  pkgs,
+}:
+treefmt-nix.lib.evalModule pkgs (
+  {pkgs, ...}: {
+    # Used to find the project root
+    projectRootFile = "flake.nix";
+
+    programs = {
+      alejandra.enable = true;
+      rustfmt.enable = true;
+      clang-format.enable = true;
+      mdformat.enable = true;
+      shfmt = {
+        enable = true;
+        indent_size = 4;
+      };
+      shellcheck.enable = true;
+      prettier = {
+        enable = true;
+        settings = {
+          arrowParens = "always";
+          bracketSameLine = false;
+          bracketSpacing = true;
+          editorconfig = true;
+          embeddedLanguageFormatting = "auto";
+          endOfLine = "lf";
+          # experimentalTernaries = false;
+          htmlWhitespaceSensitivity = "css";
+          insertPragma = false;
+          jsxSingleQuote = true;
+          printWidth = 80;
+          proseWrap = "always";
+          quoteProps = "consistent";
+          requirePragma = false;
+          semi = true;
+          singleAttributePerLine = true;
+          singleQuote = false;
+          trailingComma = "all";
+          useTabs = false;
+          vueIndentScriptAndStyle = false;
+
+          tabWidth = 2;
+        };
+      };
+      stylua.enable = true;
+      ruff = {
+        enable = true;
+        format = true;
+      };
+      taplo.enable = true;
+    };
+
+    settings = {
+      global.excludes = [
+        "CHANGELOG.md"
+        "NEWS.md"
+      ];
+      formatter = {
+        clang-format = {
+          options = ["--style" "GNU"];
+        };
+        shfmt = {
+          includes = ["*.bash"];
+        };
+      };
+    };
+  }
+)
diff --git a/update.sh b/update.sh
new file mode 100755
index 0000000..3c615f4
--- /dev/null
+++ b/update.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env sh
+
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This file is part of Quotify - A simple CLI utility to shell quote the text
+# inputted into it.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/agpl.txt>.
+
+nix flake update
+
+[ "$1" = "upgrade" ] && cargo upgrade
+cargo update