diff --git a/.github/workflows/harper.yml b/.github/workflows/harper.yml new file mode 100644 index 0000000000..8e176743f4 --- /dev/null +++ b/.github/workflows/harper.yml @@ -0,0 +1,146 @@ +name: Harper + +on: + pull_request: + workflow_dispatch: + +jobs: + harper: + name: Grammar and Style Check + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-harper + restore-keys: | + ${{ runner.os }}-cargo-harper + + - name: Install Harper CLI + run: | + if ! command -v harper-cli >/dev/null 2>&1; then + cargo install --git https://github.com/Automattic/harper harper-cli --locked + else + echo "Harper CLI found in cache." + fi + + - name: Generate Harper JSON Report + run: | + find docs src/pages -type f \( -name "*.md" -o -name "*.mdx" \) -print0 | \ + xargs -0 harper-cli lint \ + --user-dict-path ./.harper-dictionary.txt \ + --format json \ + > harper-report.json || true + + - name: Extract Spelling Candidates + run: | + python - <<'PY' + import json + + with open("harper-report.json", encoding="utf-8") as f: + reports = json.load(f) + + words = set() + + for report in reports: + for lint in report.get("lints", []): + kind = lint.get("kind") + rule = lint.get("rule") + word = lint.get("matched_text") + + if kind == "Spelling" and rule == "SpellCheck" and word: + words.add(word) + + print("Suggested dictionary candidates:") + for word in sorted(words, key=str.lower): + print(word) + PY + + - name: Check Selected Harper Rules + run: | + python - <<'PY' + import json + import re + import sys + + with open("harper-report.json", encoding="utf-8") as f: + reports = json.load(f) + + selected = [] + total_lints = 0 + + IGNORED_RULES = { + ("Spelling", "OkToOkay"), + ("Spelling", "DisjointPrefixes"), + } + + def should_ignore_spelling_text(text): + if not text: + return False + + stripped = text.strip() + + # URLs and URL fragments + if stripped.startswith(("http://", "https://", "www.")): + return True + + # File paths / command paths + if "/" in stripped: + return True + + # Long hashes, IDs, tokens + if len(stripped) >= 16 and any(c.isdigit() for c in stripped): + return True + + # Hex-like strings, e.g. d5442a5dc4baadd48b32 + if len(stripped) >= 8 and re.fullmatch(r"[a-fA-F0-9]+", stripped): + return True + + # Mostly random-looking alphanumeric tokens + if len(stripped) >= 12 and re.fullmatch(r"[A-Za-z0-9_-]+", stripped): + return True + + return False + + for report in reports: + file = report.get("file") + + for lint in report.get("lints", []): + total_lints += 1 + + kind = lint.get("kind") + rule = lint.get("rule") + line_no = lint.get("line") + message = lint.get("message") + text = lint.get("matched_text") + + if (kind, rule) in IGNORED_RULES: + continue + + if kind == "Spelling" and should_ignore_spelling_text(text): + continue + + if kind in {"Spelling", "Grammar", "Repetition"}: + selected.append( + f"{file}:{line_no}: [{kind}::{rule}] {text!r} - {message}" + ) + + print(f"Total Harper lint count: {total_lints}") + print(f"Selected lint count: {len(selected)}") + + if selected: + print("Selected Harper lints found:") + for item in selected: + print(item) + sys.exit(1) + + print("No selected Harper lints found.") + PY diff --git a/.harper-dictionary.txt b/.harper-dictionary.txt index 79a397e136..152e4462d3 100644 --- a/.harper-dictionary.txt +++ b/.harper-dictionary.txt @@ -1 +1,761 @@ HPC +DDP +SPMD +DPP +GWDG +SageMaker +Slurm +SBATCH +py +TabItem +mdx +br +vpn +html +png +SLURM +GWDG +vLLM +vllm +Gemma +NetID +CUI +SRDE +srde +OSP +GOIS +gDTN +conda +QuickLinks +QuickLink +analyses +req +cvf +rclone +nvidia +smi +Nsight +ETL +ffcv +NVIDIA +DALI +ImageNet +linux +TMPDIR +HDF5 +hdf5 +LMDB +OS +np +lmd +lmdb +jpeg +Bcolz +Zarr +Globus +nyu +inode +r +julia +OOD +du +s +hr +VSCode +mv +ln +vscode +RStudio +rstudio +ide +myquota +mailto +rclone +RClone +Gb +GB +TB +Tb +MB +Mb +Descr +pr +XXXXX +br +srun +pty +sbatch +c4 +t2 +smi +NVIDIA +NVML +P0 +P12 +nvtop +sdiag +slurmctld +DBD +rpc +cnt +Backfiring +etc +gettimeofday +seff +rpsquota +sched +sacct +walltime +alphafold +renv +knitro +amd +lammps +matlab +mathematica +crystal17 +namd +squashfs +orca +gaussian +vnc +hadoop +sas +xvfb +jupyter +schrodinger +comsol +stata +Lmod +sif +sqf +apptainer +Guano +HuggingFace +rw +ModuleNotFoundError +ro +Xeon +H200 +SIF +L40S +OCI +Infiniband +WD40 +N/A +H100 +A100 +LINPACK +Tandon +SoE +Courant +CILVR +anaconda3 +ubuntu +whoami +pwd +ood +salloc +sinfo +Act5C +admixtools +advanpix +ai +AIfSR +AllocCPUS +allowfullscreen +amanda +amdahl +AMPLab +amplgurobilink +AnyConnect +api +aql +argparse +ArgumentParser +argv +Aria2 +aria2 +AutoModel +Autoscaling +B. +B2 +bai +baiUrlTemplate +BAM +bcftools +BDGP5 +bgzip +BigBed +BigTable +bioconda +bioinformatic +Bourne +Burwood +bw +c1 +c5 +c9b4b +CACHEDIR +CANCELLED +cancelled +Cancelling +cancelling +Carahsoft +catalogue +Catalogue +cd +CentOS +centos +Ceph +cftuvSUX +cgsb +chmod +chrom +chromStart +CloudBank +CLS +cmd +Colab +ColdFront +cos6 +cp38 +cpuinfo +cpus +CRAN +cs012 +csv +cuda +cuda12 +cudnn9 +cvzf +Cyberinfrastructure +Cygwin +d8c +d8f +Dataproc +Dataproc's +DBI +debian +Debrunner +devicelogin +df +dmel +dnf +docker +DOI +DOIs +doisearch +dos2unix +dplyr +Drosophila +drwx +drwxr +drwxrwxr +DS3 +DTN +dtn +DTNs +DUA +DUAs +editfacl +edu +el9 +eleuther +emacs +Emeriti +ENV +env +envs +Esc +ExitCode +f +FACL +faidx +FAIpQLSdehngqL1xso +fb +FBgn0000042 +fc +FDT +FFTW +fftw +file1 +file2 +fileToSearch +FileZilla +FlyBase +FORC +FORTRAN +ForwardAgent +FP16 +fs +G. +ga +Gb +gcc +GCP +GCP's +GCS +gDTNs +Geene +gemini +getenv +getfacl +GFF3 +gff3 +GID +gif +GitBash +glibc +GLIBCXX +globus +gpt +GQA +gra533 +gra752 +gtf +GTF +GTF2 +Guanaco +gz +h. +HDFS +hdfs +hisat2 +HISTCONTROL +HISTSIZE +HiveQL +Horovod +hpc +HSRN +htslib +huggingface +huggingface’s +I2 +I2CC +igv +IGV +IIQ +ILSVRC +ImageNet’s +ims +ins +Internet2 +ipa +ipykernel +IRB +iso +isoforms +ItemType +j. +JBrowse +jeff +JobID +JOBID +JobName +json +JSONLines +JsonSerDe +K. +Kaggle +kb +Keras +kubernetes +LABNAME +Laiba +langchain +LESSOPEN +libmambapy +LIBPATHS +linux64 +llamaindex +llm +llvm +LMOD +lmod +lofreq +LogLevel +lp +lr +LR +lua +MacOS +macs2 +macs3 +mafft +mct +md +MD +mdw303 +mdw303test +Mehnaz +melanogaster +mem +meminfo +Miniconda +Miniforge +miniforge3 +Miniforge3 +MJSynth +mkdir +MKL +mmfs +MoabXterm +MobaKeyGen +MobaXterm +MobaXTerm +ModelCatalog +ModelCtalog +mozallowfullscreen +mozilla +MPI +mpi4py +mpiexec +MPS +Multi-Factor +multi-processor +multi-step +multi-user +MultiWorkerMirroredStrategy +MXNet +MYchdSIR4EFcYeOthelfQ +n. +n0dx20hk701 +n2 +n3 +NA +nano +Nano +ncpus +NDR400 +netcdf +NETID +netid +NetID1 +NetID2 +NetID3 +netrc +Nextflow +nfs4 +NFS4 +NFSv4 +NIST +NODELIST +NodeManagers +non-active +non-commercial +Non-Linear +nproc +ntasks +NUM +numberOfLines +nv +nvidia +o +O. +o4 +OK +OMP +OnDemand +OpenAssistant +openmpi +OpenShift +openVPN +OpenWebUI +os +OSC +out +Doing +Packrat +partition1 +pc +penv +perl +PID +pnas +pnetcdf +Portkey +Post-Processing +PostreSQL +powershell +pre-built +pre-compiled +pre-installation +pre-installed +pre-requisite +prem +printenv +project1 +ProQuest +ps +pwm +py3 +Pythia +python3 +PYTHONNOUSERSITE +PYTHONPATH +QualityOfService +Qualtrics +Quickconnect +Qwen +r6 +ra +raz +Rclone +rclone +re-running +redownloading +refSeqs +Renviron +requestor’s +reshape2 +ResNet +resrtictions +rf +rhel +rmdir +rp +Rprofile +Rscript +rsqlite +rsync +Rsync +RTS +rwx +RWX +rwxrwxr +rwxrwxrwx +RX +S3 +Sabastian +samtools +Sawe +sbin +scancel +scheduler’s +scp +segmentations +SELINUX +semi-structured +SerDe +ServerAliveInterval +setfacl +SFTP +sftp +SGLang +sgtatham +sha256 +SHLVL +sif +Simulink +Slurm +slurm +slurmstepd +Slurm’s +Solaris +sp +SQLiteStudio +squeue +sr +src +SRR307023 +SRR307024 +SRR307025 +SRR307026 +SRR307027 +SRR307028 +SRR307029 +SRR307030 +SSO +Stata's +STEAMROOT +Storable +StrictHostKeyChecking +sub-directories +sub-directory +subdir +subdir2 +sudo +susie +susie's +svc +svg +sw77 +Sxxxx +Sys +sys +Syslabs +sysparm +System32 +szip +T16 +t97br4zzvip +TAH +TBD +tc001 +TCL +tf +tgz +THB3P2vrDKcg +tibble +Tigst +tmp +tok +Top500 +Tos +Trino +tsqueryId +tsv +txt +u +U4Vmh2Rw4idTcwqzLhNX1g +UID +un-loads +un-tar +unix +UNIX +unix2dos +up +Keep +url +urlTemplate +UserKnownHostsFile +usernm +usp +usr +uv +ux +v. +v1 +v5 +VASP +VCF +vcf +venv +vertexai +VertexAI +vglrun +virtualenv +vm +w +wc +webkitallowfullscreen +webpage +Webpage +webpages +Wget +wget +whatToFind +WiFi +win10 +Windowns +WinSCP +wo +WordNet +wq +wsl +WSL2 +WSP +x. +X11 +x11 +x32 +x7 +xa0 +xa020242026 +XDG +xfs +XQuartz +xr +xvf +xvzf +XXXXSgS6Tp +xzf +yml +yourNetID +YV6MOhplKNwxXjASHYnDtM +zcat +zfs +zU +Zvvga2Y8kQZN +zypper +anyconnect +autoscaling +bam +behaviour +behaviours +cancelled +CANCELLED +cancelling +Cancelling +column1 +column2 +column3 +column4 +column6 +column7 +Customising +facl +forc +gtf +HH +huggingface’s +igv +ImageNet’s +jbrowse +JobID +kevin +kevyin +labelled +lefthand +libPaths +lr +Mathworks +md +miniforge +miniforge3 +MobaXterm +mpi +nano +nextflow +nfs4 +OK +ondemand +out +Doing +pid +portkey +rsync +rwx +scheduler’s +Slurm’s +un +unix +up +Keep +vertexai +webpage +approver +requestor’s +ImageNet’s +huggingface’s +Slurm’s +MobaXTerm +Assefa +JOBID +ImageNet +Acknowledgements +afterwards +Customizing +nnodes +CANCELLED +Cancelling +cancelling +cancelled +behaviour +Backfilling +favourite +MobaXterm +flavours +labelled +behaviours