diff --git a/rust-spell-check/Cargo.lock b/rust-spell-check/Cargo.lock new file mode 100644 index 00000000..f54f78b8 --- /dev/null +++ b/rust-spell-check/Cargo.lock @@ -0,0 +1,535 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bindgen" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap 2.34.0", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5177fac1ab67102d8989464efd043c6ff44191b1557ec1ddd489b4f7e1447e77" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim 0.10.0", + "termcolor", + "textwrap 0.14.2", +] + +[[package]] +name = "clap_derive" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d42c94ce7c2252681b5fed4d3627cc807b13dfc033246bd05d5b252399000e" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cstr" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60f0dd132e4b67f20fd764d4835d968f666ff1a2f59e432983d168b98424deb" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libaspell-sys" +version = "0.1.0" +dependencies = [ + "bindgen", +] + +[[package]] +name = "libc" +version = "0.2.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "spell-checker" +version = "0.1.0" +dependencies = [ + "clap 3.1.2", + "cstr", + "libaspell-sys", + "tree-sitter", + "tree-sitter-c", + "tree-sitter-cpp", + "tree-sitter-python", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" + +[[package]] +name = "tree-sitter" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e34327f8eac545e3f037382471b2b19367725a242bba7bc45edb9efb49fe39a" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-c" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bdc5574c6cbc39c409246caeb1dd4d3c4bd6d30d4e9b399776086c20365fd24" +dependencies = [ + "cc", + "tree-sitter", +] + +[[package]] +name = "tree-sitter-cpp" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a869e3c5cef4e5db4e9ab16a8dc84d73010e60ada14cdc60d2f6d8aed17779d" +dependencies = [ + "cc", + "tree-sitter", +] + +[[package]] +name = "tree-sitter-python" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c46916553ebc2a5b23763cd2da8d2b104c515c8f828eb678d1477ccd8c379c" +dependencies = [ + "cc", + "tree-sitter", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "which" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/rust-spell-check/Cargo.toml b/rust-spell-check/Cargo.toml new file mode 100644 index 00000000..a277e5f6 --- /dev/null +++ b/rust-spell-check/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "spell-checker" +version = "0.1.0" +edition = "2021" + +[dependencies] +tree-sitter = "0.20.4" +tree-sitter-c = "0.20.1" +tree-sitter-cpp = "0.20.0" +tree-sitter-python = "0.19.1" +libaspell-sys = { path = "crates/libaspell-sys" } +cstr = "0.2.10" +clap = { version = "3.1.2", features = ["derive"] } + +[workspace] +members = ["crates/libaspell-sys"] + +[profile.release] +lto = "thin" +panic = "abort" diff --git a/rust-spell-check/action.yml b/rust-spell-check/action.yml new file mode 100644 index 00000000..daa00ace --- /dev/null +++ b/rust-spell-check/action.yml @@ -0,0 +1,148 @@ +name: 'rust-spell-check' +description: 'Rust CI spellings check' +inputs: + path: + description: 'Path to repository folder to check spellings in.' + required: false + default: ./ + lexicon: + description: 'Path to lexicon file to check spellings with' + required: false + default: lexicon.txt + exclude-dirs: + description: "Comma separated list of directories to not spell check" + required: false + exclude-files: + description: "Comma separated list of files to not spell check" + required: false + include-extensions: + description: "Comma separated list of files to match to regex" + required: false + + +runs: + using: "composite" + steps: + - env: + bashPass: \033[32;1mPASSED - + bashInfo: \033[33;1mINFO - + bashFail: \033[31;1mFAILED - + bashEnd: \033[0m + stepName: Set-Up The Spell Checker + name: ${{ env.stepName }} + id: spell-checker-setup + shell: bash + run: | + # ${{ env.stepName }} + echo "::group::Install Dependencies" + + # Install the Dependencies we need to run the spell-checker + sudo apt-get install util-linux -y + sudo apt-get install fd-find -y + sudo apt-get install aspell -y + sudo apt-get install spell -y + echo "::endgroup::" + + # Add the current directory to PATH + export PATH="$GITHUB_ACTION_PATH:$PATH" + + + # Due to feedback from @archigup we will not be storing the binary + # So will build it from scratch each time. A future improvement is to use + # GitHub Caches or a public S3 bucket to store a pre-built one and download it. + # When this is done this logic below needs to be changed to perform the download + # And then try to use the spell-checker. Leaving this here for future reference + # When this work is being done. + # echo -e " ${{ env.bashInfo }} Try Using the pre-built spell checker ${{ env.bashEnd }}" + # Wrap the check to use if in a set +e so we don't error out if it fails + # Save the exit code to check later, as "set -e" will overwrite it + # set +e + # spell-checker + # exitCode=$? + # set -e + + echo "::group::Compile Spell Checker" + exitCode=1 + + if ! [ $exitCode -eq 0 ]; then + #echo -e " ${{ env.bashFail }} Don't have the ability to use the current spell checker, building it ${{ env.bashEnd }}" + + # If we can't run the current one, install the tools we need to build it + # Run one a time for error checking + sudo apt-get install libaspell-dev -y + sudo apt-get install build-essential -y + sudo apt install rustc -y + + echo -e "${{ env.bashInfo }} cargo --version = $(cargo --version) ${{ env.bashEnd }}" + echo -e "${{ env.bashInfo }} rustc --version = $(rustc --version) ${{ env.bashEnd }}" + + pushd "$GITHUB_ACTION_PATH" + cargo build --release + echo -e "find = $(find . -name 'spell-checker') " + # It's possible that we have one in the directory, but just can't suse it + # set +e so we don't error when overriding it + set +e + mv $(find . -name "spell-checker") . + set -e + popd + spell-checker --help + echo "::endgroup::Compile Spell Checker" + + # Only make it to here if nothing above fails + echo -e "${{ env.bashPass }} Compiled the Spell Checker ${{ env.bashEnd }}" + fi + echo "::endgroup::" + + # Only get to here if nothing above fails + echo -e "${{ env.bashPass }} ${{ env.stepName }} ${{ env.bashEnd }}" + + - env: + bashPass: \033[32;1mPASSED - + bashInfo: \033[33;1mINFO - + bashFail: \033[31;1mFAILED - + bashEnd: \033[0m + stepName: Spell Checker + name: ${{ env.stepName }} + id: run-spell-checker + working-directory: ${{ inputs.path }} + shell: bash + run: | + # ${{ env.stepName }} + #echo "::group::${{ env.stepName }}" + export PATH="$GITHUB_ACTION_PATH:$PATH" + + # So here's the deal. At time of writing this spell checker can check + # every word in every folder in FreeRTOS/FreeRTOS in like 10 seconds. + # So I just let it do that. If this changes in the future, feel free to use + # The various exclude dir/file options + # files=$(getFiles --exclude-dirs="${{ inputs.exclude-dirs}}" --exclude-files="${{ inputs.exclude-files }}" --include-extensions="${{ inputs.include-extensions }}") + + # The use of exec will return the exit code from the grep command + # Grep returns an exit code if a file isn't found + # So wrap the search in a set +/- e so github doesn't stop the run on first failure + set +e + files=$(fdfind -e c -e h --exec grep -liE "copyright (.*) [0-9]{4} amazon.com") + set -e + + # If you're onboarding a repo or need better debugging, uncomment this instead + # Of the one-line check + # for file in ${files[@]}; do + # echo -e "${{ env.bashInfo }} Checking spelling of "$file" ${{ env.bashEnd }}" + # set +e + # spell-checker -c -w .cSpellWords.txt $file + # exitStatus=$? + # set -e + # done + + set +e + spell-checker -c -w .cSpellWords.txt $files + exitStatus=$? + set -e + + #echo "::endgroup::" + if [ $exitStatus -eq 0 ]; then + echo -e "${{ env.bashPass }} ${{ env.stepName }} ${{ env.bashEnd }}" + else + echo -e "${{ env.bashFail }} ${{ env.stepName }} ${{ env.bashEnd }}" + exit 1 + fi diff --git a/rust-spell-check/crates/libaspell-sys/Cargo.toml b/rust-spell-check/crates/libaspell-sys/Cargo.toml new file mode 100644 index 00000000..57a114ae --- /dev/null +++ b/rust-spell-check/crates/libaspell-sys/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "libaspell-sys" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[build-dependencies] +bindgen = "0.59.2" diff --git a/rust-spell-check/crates/libaspell-sys/build.rs b/rust-spell-check/crates/libaspell-sys/build.rs new file mode 100644 index 00000000..18041268 --- /dev/null +++ b/rust-spell-check/crates/libaspell-sys/build.rs @@ -0,0 +1,18 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + println!("cargo:rustc-link-lib=aspell"); + println!("cargo:rerun-if-changed=wrapper.h"); + + let bindings = bindgen::Builder::default() + .header("wrapper.h").clang_arg("-I/opt/homebrew/Cellar/aspell/0.60.8/include/") + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/rust-spell-check/crates/libaspell-sys/src/lib.rs b/rust-spell-check/crates/libaspell-sys/src/lib.rs new file mode 100644 index 00000000..a38a13a8 --- /dev/null +++ b/rust-spell-check/crates/libaspell-sys/src/lib.rs @@ -0,0 +1,5 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rust-spell-check/crates/libaspell-sys/wrapper.h b/rust-spell-check/crates/libaspell-sys/wrapper.h new file mode 100644 index 00000000..b5c95cfe --- /dev/null +++ b/rust-spell-check/crates/libaspell-sys/wrapper.h @@ -0,0 +1 @@ +#include diff --git a/rust-spell-check/queries/c.scm b/rust-spell-check/queries/c.scm new file mode 100644 index 00000000..178355c6 --- /dev/null +++ b/rust-spell-check/queries/c.scm @@ -0,0 +1,2 @@ +(comment) @comment +(string_literal) @string diff --git a/rust-spell-check/queries/python.scm b/rust-spell-check/queries/python.scm new file mode 100644 index 00000000..8a58e304 --- /dev/null +++ b/rust-spell-check/queries/python.scm @@ -0,0 +1,2 @@ +(comment) @comment +(string) @string diff --git a/rust-spell-check/src/main.rs b/rust-spell-check/src/main.rs new file mode 100644 index 00000000..8c8b2fbf --- /dev/null +++ b/rust-spell-check/src/main.rs @@ -0,0 +1,266 @@ +#![deny(clippy::all, clippy::pedantic)] + +use clap::Parser; +use libaspell_sys::{AspellDocumentChecker, AspellSpeller}; +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::exit, +}; + +/// Spell checker for code repos +#[derive(Parser)] +struct Args { + /// Additional wordlists to use + #[clap(long, short)] + wordlist: Vec, + /// Show context of misspellings + #[clap(long, short)] + context: bool, + /// Files to check for spelling errors + files: Vec, +} + +fn main() { + let args = Args::parse(); + + let error_count = args + .files + .iter() + .map(|f| process_file(f, args.context, &args.wordlist)) + .sum(); + + exit(error_count); +} + +fn process_file>(path: P, context: bool, wordlists: &[PathBuf]) -> i32 { + let path = path.as_ref(); + let path_str = path.display(); + + let contents = match fs::read_to_string(path) { + Ok(s) => s, + Err(e) => { + eprintln!("Error reading {}: {}", path_str, e); + exit(-1); + } + }; + + let mut speller = match SpellChecker::new(wordlists) { + Ok(s) => s, + Err(e) => { + eprintln!("Error initializing aspell: {}", e); + exit(-1); + } + }; + + let regions: Box>> = match path + .extension() + .and_then(OsStr::to_str) + { + Some("c" | "h") => Box::new(extract_regions_treesitter( + &contents, + tree_sitter_c::language(), + include_str!("../queries/c.scm"), + )), + Some("cc" | "cpp" | "cxx" | "hh" | "hpp" | "hxx") => Box::new(extract_regions_treesitter( + &contents, + tree_sitter_cpp::language(), + include_str!("../queries/c.scm"), + )), + Some("py") => Box::new(extract_regions_treesitter( + &contents, + tree_sitter_python::language(), + include_str!("../queries/python.scm"), + )), + _ => Box::new(extract_regions_text(&contents)), + }; + + let mut errors = 0; + + regions.for_each(|r| { + speller.check_line(r.text).for_each(|(index, len)| { + errors += 1; + + let join_str; + let spell_error_str = if context { + join_str = [ + &r.text[..index], + "\x1B[0;31m", // Red + &r.text[index..index + len], + "\x1B[0m", // Clear formatting + &r.text[index + len..], + ] + .join(""); + &join_str + } else { + &r.text[index..index + len] + }; + + println!( + "{} ({},{}): {}", + path_str, + r.line, + r.offset + index, + spell_error_str + ); + }); + }); + + errors +} + +struct LineRegion<'a> { + line: usize, + offset: usize, + text: &'a str, +} + +fn extract_regions_text(text: &str) -> impl Iterator> { + text.lines().enumerate().map(|(n, l)| LineRegion { + line: n + 1, + offset: 0, + text: l, + }) +} + +fn extract_regions_treesitter<'a>( + text: &'a str, + language: tree_sitter::Language, + query: &str, +) -> impl Iterator> { + use tree_sitter::{Parser, Query, QueryCursor}; + + let mut parser = Parser::new(); + + parser + .set_language(language) + .expect("Error loading treesitter language"); + + let tree = parser.parse(text, None).expect("Error parsing file"); + let root_node = tree.root_node(); + + QueryCursor::new() + .matches( + &Query::new(language, query).expect("Error loading query"), + root_node, + text.as_bytes(), + ) + .map(|m| m.captures[0].node) + .flat_map(|n| { + n.utf8_text(text.as_bytes()) + .expect("utf8 parsing error") + .lines() + .enumerate() + .map(move |(i, l)| LineRegion { + line: n.start_position().row + i + 1, + offset: if i == 0 { n.start_position().column } else { 0 }, + text: l, + }) + }) + .collect::>() + .into_iter() +} + +struct SpellChecker { + doc_checker: *mut AspellDocumentChecker, + spell_checker: *mut AspellSpeller, +} + +impl SpellChecker { + fn new(wordlists: &[PathBuf]) -> Result { + use cstr::cstr; + use libaspell_sys::{ + aspell_config_replace, aspell_error_message, aspell_error_number, delete_aspell_config, + delete_aspell_speller, new_aspell_config, new_aspell_document_checker, + new_aspell_speller, to_aspell_document_checker, to_aspell_speller, + }; + use std::ffi::{CStr, CString}; + + let spell_checker = unsafe { + let config = new_aspell_config(); + + aspell_config_replace(config, cstr!("lang").as_ptr(), cstr!("en_US").as_ptr()); + aspell_config_replace(config, cstr!("camel-case").as_ptr(), cstr!("true").as_ptr()); + aspell_config_replace( + config, + cstr!("run-together").as_ptr(), + cstr!("true").as_ptr(), + ); + for l in wordlists { + let list = CString::new(l.to_str().unwrap()).unwrap(); + aspell_config_replace(config, cstr!("add-wordlists").as_ptr(), list.as_ptr()); + } + + let result = new_aspell_speller(config); + + delete_aspell_config(config); + + if aspell_error_number(result) != 0 { + return Err(CStr::from_ptr(aspell_error_message(result)) + .to_str() + .unwrap()); + } + + to_aspell_speller(result) + }; + + let doc_checker = unsafe { + let result = new_aspell_document_checker(spell_checker); + + if aspell_error_number(result) != 0 { + delete_aspell_speller(spell_checker); + return Err(CStr::from_ptr(aspell_error_message(result)) + .to_str() + .unwrap()); + } + + to_aspell_document_checker(result) + }; + + Ok(SpellChecker { + doc_checker, + spell_checker, + }) + } + + fn check_line<'a>(&'a mut self, line: &str) -> SpellCheckerErrors<'a> { + use libaspell_sys::aspell_document_checker_process; + unsafe { + aspell_document_checker_process( + self.doc_checker, + line.as_ptr().cast::(), + line.len().try_into().expect("Line longer than i32 max."), + ); + } + SpellCheckerErrors { checker: self } + } +} + +impl Drop for SpellChecker { + fn drop(&mut self) { + use libaspell_sys::{delete_aspell_document_checker, delete_aspell_speller}; + unsafe { + delete_aspell_document_checker(self.doc_checker); + delete_aspell_speller(self.spell_checker); + } + } +} + +struct SpellCheckerErrors<'a> { + checker: &'a mut SpellChecker, +} + +impl<'a> Iterator for SpellCheckerErrors<'a> { + type Item = (usize, usize); + + fn next(&mut self) -> Option { + use libaspell_sys::aspell_document_checker_next_misspelling; + let token = unsafe { aspell_document_checker_next_misspelling(self.checker.doc_checker) }; + if token.len == 0 { + None + } else { + Some((token.offset as usize, token.len as usize)) + } + } +}