├── .0pdd.yml ├── .gitattributes ├── .github └── workflows │ ├── actionlint.yml │ ├── codecov.yml │ ├── copyrights.yml │ ├── license.yml │ ├── markdown-lint.yml │ ├── pdd.yml │ ├── rake.yml │ ├── reuse.yml │ ├── typos.yml │ ├── xcop.yml │ └── yamllint.yml ├── .gitignore ├── .pdd ├── .rubocop.yml ├── .rultor.yml ├── .simplecov ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── LICENSES └── MIT.txt ├── README.md ├── REUSE.toml ├── Rakefile ├── bin └── texqc ├── cucumber.yml ├── features ├── cli.feature ├── gem_package.feature ├── step_definitions │ └── steps.rb └── support │ └── env.rb ├── logo.svg ├── renovate.json └── texqc.gemspec /.0pdd.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | errors: 5 | - yegor256@gmail.com 6 | # alerts: 7 | # github: 8 | # - yegor256 9 | 10 | tags: 11 | - pdd 12 | - bug 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Check out all text files in UNIX format, with LF as end of line 2 | # Don't change this file. If you have any ideas about it, please 3 | # submit a separate issue about it and we'll discuss. 4 | 5 | * text=auto eol=lf 6 | *.rb ident 7 | *.xml ident 8 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: actionlint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | actionlint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Download actionlint 20 | id: get_actionlint 21 | run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 22 | shell: bash 23 | - name: Check workflow files 24 | run: ${{ steps.get_actionlint.outputs.executable }} -color 25 | shell: bash 26 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: codecov 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | jobs: 11 | codecov: 12 | timeout-minutes: 15 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 3.3 19 | bundler-cache: true 20 | - run: bundle config set --global path "$(pwd)/vendor/bundle" 21 | - run: bundle install --no-color 22 | - run: bundle exec rake 23 | - uses: codecov/codecov-action@v5 24 | with: 25 | token: ${{ secrets.CODECOV_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/copyrights.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: copyrights 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | copyrights: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: yegor256/copyrights-action@0.0.8 20 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: license 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | license: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - shell: bash 20 | run: | 21 | header="Copyright (c) $(date +%Y) Yegor Bugayenko" 22 | failed="false" 23 | while IFS= read -r file; do 24 | if ! grep -q "${header}" "${file}"; then 25 | failed="true" 26 | echo "⚠️ Copyright header is not found in: ${file}" 27 | else 28 | echo "File looks good: ${file}" 29 | fi 30 | done < <(find . -type f \( \ 31 | -name "Dockerfile" -o \ 32 | -name "LICENSE.txt" -o \ 33 | -name "Makefile" -o \ 34 | -name "Rakefile" -o \ 35 | -name "*.sh" -o \ 36 | -name "*.rb" -o \ 37 | -name "*.fe" -o \ 38 | -name "*.yml" \ 39 | \) -print) 40 | if [ "${failed}" = "true" ]; then 41 | exit 1 42 | fi 43 | -------------------------------------------------------------------------------- /.github/workflows/markdown-lint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: markdown-lint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | paths-ignore: ['paper/**', 'sandbox/**'] 14 | concurrency: 15 | group: markdown-lint-${{ github.ref }} 16 | cancel-in-progress: true 17 | jobs: 18 | markdown-lint: 19 | timeout-minutes: 15 20 | runs-on: ubuntu-24.04 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: DavidAnson/markdownlint-cli2-action@v20.0.0 24 | -------------------------------------------------------------------------------- /.github/workflows/pdd.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: pdd 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | pdd: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: volodya-lombrozo/pdd-action@master 20 | -------------------------------------------------------------------------------- /.github/workflows/rake.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: rake 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | rake: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-24.04, macos-15, windows-2022] 18 | ruby: [3.3] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: ${{ matrix.ruby }} 25 | bundler-cache: true 26 | - run: bundle config set --global path "$(pwd)/vendor/bundle" 27 | - run: bundle install --no-color 28 | - run: bundle exec rake 29 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: reuse 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | reuse: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: fsfe/reuse-action@v5 20 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: typos 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | typos: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: crate-ci/typos@v1.32.0 20 | -------------------------------------------------------------------------------- /.github/workflows/xcop.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: xcop 6 | 'on': 7 | push: 8 | pull_request: 9 | jobs: 10 | xcop: 11 | timeout-minutes: 15 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: g4s8/xcop-action@master 16 | -------------------------------------------------------------------------------- /.github/workflows/yamllint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: yamllint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | yamllint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ibiqlik/action-yamllint@v3 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | .DS_Store 3 | .idea/ 4 | .yardoc/ 5 | *.gem 6 | coverage/ 7 | doc/ 8 | node_modules/ 9 | rdoc/ 10 | tmp/ 11 | vendor/ 12 | -------------------------------------------------------------------------------- /.pdd: -------------------------------------------------------------------------------- 1 | --source=. 2 | --verbose 3 | --exclude target/**/* 4 | --exclude coverage/**/* 5 | --rule min-words:20 6 | --rule min-estimate:15 7 | --rule max-estimate:90 8 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | AllCops: 5 | Exclude: 6 | - 'assets/**/*' 7 | - 'vendor/**/*' 8 | DisplayCopNames: true 9 | TargetRubyVersion: 2.3 10 | Metrics/LineLength: 11 | Max: 100 12 | Layout/EndOfLine: 13 | EnforcedStyle: lf 14 | Style/MultilineTernaryOperator: 15 | Enabled: false 16 | Metrics/BlockLength: 17 | Max: 50 18 | Layout/AlignParameters: 19 | Enabled: false 20 | Layout/EmptyLineAfterGuardClause: 21 | Enabled: false 22 | require: 23 | - rubocop-rake 24 | - rubocop-minitest 25 | - rubocop-performance 26 | -------------------------------------------------------------------------------- /.rultor.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | assets: 6 | rubygems.yml: yegor256/home#assets/rubygems.yml 7 | install: | 8 | export PATH=$PATH:/usr/local/texlive/2021/bin/x86_64-linux/ 9 | sudo apt install -y aspell 10 | pdd -f /dev/null 11 | sudo bundle install --no-color "--gemfile=$(pwd)/Gemfile" 12 | release: 13 | pre: false 14 | script: |- 15 | bundle exec rake 16 | sed -i "s/0\.0\.0/${tag}/g" bin/texqc 17 | sed -i "s/0\.0\.0/${tag}/g" texqc.gemspec 18 | git add bin/texqc 19 | git add texqc.gemspec 20 | git commit -m "version set to ${tag}" 21 | gem build texqc.gemspec 22 | chmod 0600 ../rubygems.yml 23 | gem push *.gem --config-file ../rubygems.yml 24 | merge: 25 | script: |- 26 | bundle exec rake 27 | deploy: 28 | script: |- 29 | echo "There is nothing to deploy" 30 | exit -1 31 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | 4 | if Gem.win_platform? then 5 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 6 | SimpleCov::Formatter::HTMLFormatter 7 | ] 8 | SimpleCov.start do 9 | add_filter "/test/" 10 | add_filter "/features/" 11 | end 12 | else 13 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( 14 | [SimpleCov::Formatter::HTMLFormatter] 15 | ) 16 | SimpleCov.start do 17 | add_filter "/test/" 18 | add_filter "/features/" 19 | minimum_coverage 60 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | source 'https://rubygems.org' 7 | gemspec 8 | 9 | gem 'rubocop-minitest', '>0', require: false 10 | gem 'rubocop-performance', '>0', require: false 11 | gem 'rubocop-rake', '>0', require: false 12 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | texqc (0.0.0) 5 | backtrace (~> 0.3) 6 | loog (~> 0.2) 7 | slop (~> 4.8.2) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | ast (2.4.3) 13 | backtrace (0.4.0) 14 | builder (3.3.0) 15 | codecov (0.2.6) 16 | colorize 17 | json 18 | simplecov 19 | colorize (1.1.0) 20 | cucumber (1.3.20) 21 | builder (>= 2.1.2) 22 | diff-lcs (>= 1.1.3) 23 | gherkin (~> 2.12) 24 | multi_json (>= 1.7.5, < 2.0) 25 | multi_test (>= 0.1.2) 26 | diff-lcs (1.6.2) 27 | docile (1.4.1) 28 | gherkin (2.12.2) 29 | multi_json (~> 1.3) 30 | jaro_winkler (1.5.6) 31 | json (2.12.0) 32 | logger (1.7.0) 33 | loog (0.6.1) 34 | logger (~> 1.0) 35 | multi_json (1.15.0) 36 | multi_test (1.1.0) 37 | parallel (1.27.0) 38 | parser (3.3.8.0) 39 | ast (~> 2.4.1) 40 | racc 41 | powerpack (0.1.3) 42 | racc (1.8.1) 43 | rainbow (3.1.1) 44 | rake (12.3.3) 45 | rubocop (0.61.0) 46 | jaro_winkler (~> 1.5.1) 47 | parallel (~> 1.10) 48 | parser (>= 2.5, != 2.5.1.1) 49 | powerpack (~> 0.1) 50 | rainbow (>= 2.2.2, < 4.0) 51 | ruby-progressbar (~> 1.7) 52 | unicode-display_width (~> 1.4.0) 53 | rubocop-minitest (0.0.1) 54 | rubocop-performance (1.0.0) 55 | rubocop (>= 0.58.0) 56 | rubocop-rake (0.5.1) 57 | rubocop 58 | rubocop-rspec (1.31.0) 59 | rubocop (>= 0.60.0) 60 | ruby-progressbar (1.13.0) 61 | simplecov (0.22.0) 62 | docile (~> 1.1) 63 | simplecov-html (~> 0.11) 64 | simplecov_json_formatter (~> 0.1) 65 | simplecov-html (0.13.1) 66 | simplecov_json_formatter (0.1.4) 67 | slop (4.8.2) 68 | unicode-display_width (1.4.1) 69 | 70 | PLATFORMS 71 | arm64-darwin-23 72 | arm64-darwin-24 73 | ruby 74 | x64-mingw-ucrt 75 | x86_64-linux 76 | 77 | DEPENDENCIES 78 | codecov (= 0.2.6) 79 | cucumber (~> 1.3.17) 80 | rake (= 12.3.3) 81 | rubocop (= 0.61.0) 82 | rubocop-minitest (> 0) 83 | rubocop-performance (> 0) 84 | rubocop-rake (> 0) 85 | rubocop-rspec (= 1.31.0) 86 | texqc! 87 | 88 | BUNDLED WITH 89 | 2.5.16 90 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2020 Yegor Bugayenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the 'Software'), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2020 Yegor Bugayenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the 'Software'), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Gem Version](https://badge.fury.io/rb/texqc.svg)](https://badge.fury.io/rb/texqc) 4 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/takes/texqc/master/LICENSE.txt) 5 | 6 | This tool helps you make sure your LaTeX document compiles without issues. 7 | 8 | First, you install it: 9 | 10 | ```bash 11 | $ gem install texqc 12 | ``` 13 | 14 | Then, you just run it after the LaTeX document is compiled: 15 | 16 | ```bash 17 | $ latexmk -pdf article 18 | $ texqc article 19 | ``` 20 | 21 | If any warnings were reported by LaTeX, you will get a short list of them 22 | and the exit code will be non-zero (very convenient for your CI/CD scripts). 23 | 24 | To make configuration easier, you can create `.texqc` file next to your 25 | `.tex` file and place all your command line configuration options over there, 26 | each one on its own line. You can also have a global configuration file 27 | at `~/.texqc`, which will be read first. 28 | 29 | ## How to contribute 30 | 31 | Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html). 32 | Make sure your build is green before you contribute 33 | your pull request. You will need to have [Ruby](https://www.ruby-lang.org/en/) 2.3+ and 34 | [Bundler](https://bundler.io/) installed. Then: 35 | 36 | ``` 37 | $ bundle update 38 | $ bundle exec rake 39 | ``` 40 | 41 | If it's clean and you don't see any error messages, submit your pull request. 42 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | 4 | version = 1 5 | [[annotations]] 6 | path = [ 7 | ".DS_Store", 8 | ".gitattributes", 9 | ".gitignore", 10 | ".pdd", 11 | "**.json", 12 | "**.md", 13 | "**.svg", 14 | "**.txt", 15 | "**/.DS_Store", 16 | "**/.gitignore", 17 | "**/.pdd", 18 | "**/*.csv", 19 | "**/*.jpg", 20 | "**/*.json", 21 | "**/*.md", 22 | "**/*.pdf", 23 | "**/*.png", 24 | "**/*.svg", 25 | "**/*.txt", 26 | "**/*.vm", 27 | "**/CNAME", 28 | "**/Gemfile.lock", 29 | "Gemfile.lock", 30 | "README.md", 31 | "renovate.json", 32 | ] 33 | precedence = "override" 34 | SPDX-FileCopyrightText = "Copyright (c) 2025 Yegor Bugayenko" 35 | SPDX-License-Identifier = "MIT" 36 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'rubygems' 7 | require 'rake' 8 | require 'rdoc' 9 | require 'rake/clean' 10 | 11 | def name 12 | @name ||= File.basename(Dir['*.gemspec'].first, '.*') 13 | end 14 | 15 | def version 16 | Gem::Specification.load(Dir['*.gemspec'].first).version 17 | end 18 | 19 | task default: %i[clean features rubocop] 20 | 21 | require 'rubocop/rake_task' 22 | RuboCop::RakeTask.new(:rubocop) do |task| 23 | task.fail_on_error = true 24 | task.requires << 'rubocop-rspec' 25 | end 26 | 27 | require 'cucumber/rake/task' 28 | Cucumber::Rake::Task.new(:features) do 29 | Rake::Cleaner.cleanup_files(['coverage']) 30 | end 31 | Cucumber::Rake::Task.new(:'features:html') do |t| 32 | t.profile = 'html_report' 33 | end 34 | -------------------------------------------------------------------------------- /bin/texqc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 Yegor Bugayenko 5 | # SPDX-License-Identifier: MIT 6 | 7 | VERSION = '0.0.0' 8 | 9 | STDOUT.sync = true 10 | 11 | require 'backtrace' 12 | require 'loog' 13 | require 'open3' 14 | require 'slop' 15 | 16 | # Errors counter. 17 | class Errors 18 | attr_reader :count 19 | def initialize 20 | @count = 0 21 | end 22 | 23 | def report(pos, txt) 24 | puts "[#{pos}] #{txt}" 25 | @count += 1 26 | end 27 | end 28 | 29 | def config(path) 30 | f = File.expand_path(path) 31 | args = [] 32 | if File.exist?(f) 33 | args += File.readlines(f).map(&:strip) 34 | puts "Found #{args.length} lines in #{File.absolute_path(f)}" 35 | end 36 | args 37 | end 38 | 39 | begin 40 | log = Loog::REGULAR 41 | args = config('~/.texqc') + config('.texqc') + ARGV 42 | 43 | begin 44 | opts = Slop.parse(args, strict: true, help: true) do |o| 45 | o.banner = "Usage (#{VERSION}): texqc [options] file... 46 | Options are:" 47 | o.bool '--dry', 'Don\'t fail the build on errors' 48 | o.array '--ignore', 'Ignore given regexp' 49 | o.bool '--version', 'Print current version' do 50 | puts VERSION 51 | exit 52 | end 53 | o.bool '--verbose', 'Make it more verbose than usual' do 54 | log = Loog::VERBOSE 55 | end 56 | o.bool '--help', 'Read this: https://github.com/yegor256/texqc' do 57 | puts o 58 | exit 59 | end 60 | end 61 | rescue Slop::Error => ex 62 | raise ex.message 63 | end 64 | candidates = opts.arguments 65 | candidates += Dir['*.tex'] if candidates.empty? 66 | puts "Args: #{args}" if opts[:verbose] 67 | puts "Ignore: #{opts[:ignore]}" if opts[:verbose] 68 | puts "Candidates: #{candidates}" if opts[:verbose] 69 | candidates.each do |doc| 70 | if doc.end_with?('.tex') 71 | log.info("File extention removed from #{doc.inspect}") 72 | doc = doc.gsub(/\.tex$/, '') 73 | end 74 | f = "#{doc}.log" 75 | e = Errors.new 76 | matches = [ 77 | /^Underfull /, 78 | /^Overfull /, 79 | /^LaTeX Warning: /, 80 | /^pdfTeX warning: /, 81 | /^Class [a-zA-Z0-9\*]+ Warning: /, 82 | /^Package [a-zA-Z0-9\*]+ Warning: /, 83 | /^LaTeX Font Warning: / 84 | ] 85 | File.readlines(f).each_with_index do |t, i| 86 | matches.each do |p| 87 | next unless p.match?(t) 88 | next if opts[:ignore].any? { |re| /.*#{re}.*/.match?(t) } 89 | e.report(i, t) 90 | end 91 | end 92 | unless e.count.zero? 93 | log.info("#{e.count} LaTeX processing errors found in #{f.inspect}") 94 | exit 1 95 | end 96 | log.info("No LaTeX processing errors found in #{f.inspect}") 97 | end 98 | rescue StandardError => ex 99 | if opts[:verbose] 100 | puts Backtrace.new(ex).to_s 101 | else 102 | puts "ERROR: #{ex.message}" 103 | end 104 | exit(255) 105 | end 106 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | default: --format pretty 5 | travis: --format progress 6 | html_report: --format progress --format html --out=features_report.html 7 | -------------------------------------------------------------------------------- /features/cli.feature: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | Feature: Command Line Processing 4 | As a author of LaTeX document I want to check spelling 5 | 6 | Scenario: Help can be printed 7 | When I run bin/texqc with "--help" 8 | Then Exit code is zero 9 | And Stdout contains "--help" 10 | 11 | Scenario: Good LaTeX log output can be checked 12 | Given I have a "article.tex" file with content: 13 | """ 14 | \documentclass{article} 15 | \begin{document} 16 | How are you, my dear friend? 17 | \end{document} 18 | """ 19 | When I run bash with "pdflatex article.tex" 20 | Then I run bin/texqc with "" 21 | Then Exit code is zero 22 | And Stdout contains "No LaTeX processing errors found" 23 | 24 | Scenario: Bad LaTeX log output checked 25 | Given I have a "article.tex" file with content: 26 | """ 27 | \documentclass{article} 28 | \begin{document} 29 | HowareyouHowareyouHowareyouHowareyouHowareyouHowareyouHowareyouHowareyouHowareyouHowareyouHowareyouHowareyouHowareyou 30 | \end{document} 31 | """ 32 | When I run bash with "pdflatex article.tex" 33 | Then I run bin/texqc with "article" 34 | Then Exit code is not zero 35 | And Stdout contains "1 LaTeX processing errors" 36 | 37 | Scenario: Bad LaTeX log output checked with LaTeX warning 38 | Given I have a "article.tex" file with content: 39 | """ 40 | \documentclass{article} 41 | \begin{document} 42 | test\label{xxx}test\label{xxx} 43 | \end{document} 44 | """ 45 | When I run bash with "pdflatex article.tex" 46 | Then I run bin/texqc with "article.tex" 47 | Then Exit code is not zero 48 | And Stdout contains "1 LaTeX processing errors" 49 | 50 | Scenario: Bad LaTeX log output checked with LaTeX warning, but ignored 51 | Given I have a "article.tex" file with content: 52 | """ 53 | \documentclass{article} 54 | \begin{document} 55 | test\label{xxx}test\label{xxx} 56 | \end{document} 57 | """ 58 | When I run bash with "pdflatex article.tex" 59 | Then I run bin/texqc with "--ignore 'may have changed' article.tex" 60 | Then Exit code is zero 61 | 62 | Scenario: Bad LaTeX log output checked with LaTeX warning, but ignored with .texqc 63 | Given I have a "article.tex" file with content: 64 | """ 65 | \documentclass{article} 66 | \begin{document} 67 | test\label{xxx}test\label{xxx} 68 | \end{document} 69 | """ 70 | And I have a ".texqc" file with content: 71 | """ 72 | --verbose 73 | 74 | --ignore='may have changed' 75 | """ 76 | When I run bash with "pdflatex article.tex" 77 | Then I run bin/texqc 78 | Then Exit code is zero 79 | -------------------------------------------------------------------------------- /features/gem_package.feature: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2020 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | Feature: Gem Package 4 | As a source code writer I want to be able to 5 | package the Gem into .gem file 6 | 7 | Scenario: Gem can be packaged 8 | Given It is Unix 9 | Given I have a "execs.rb" file with content: 10 | """ 11 | #!/usr/bin/env ruby 12 | require 'rubygems' 13 | spec = Gem::Specification::load('./spec.rb') 14 | if spec.executables.empty? 15 | fail 'no executables: ' + File.read('./spec.rb') 16 | end 17 | """ 18 | When I run bash with: 19 | """ 20 | cd texqc 21 | gem build texqc.gemspec 22 | gem specification --ruby texqc-*.gem > ../spec.rb 23 | cd .. 24 | ruby execs.rb 25 | """ 26 | Then Exit code is zero 27 | -------------------------------------------------------------------------------- /features/step_definitions/steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'tmpdir' 7 | require 'English' 8 | 9 | Before do 10 | @cwd = Dir.pwd 11 | @dir = Dir.mktmpdir('test') 12 | FileUtils.mkdir_p(@dir) unless File.exist?(@dir) 13 | Dir.chdir(@dir) 14 | end 15 | 16 | After do 17 | Dir.chdir(@cwd) 18 | FileUtils.rm_rf(@dir) if File.exist?(@dir) 19 | end 20 | 21 | Given(/^I have a "([^"]*)" file with content:$/) do |file, text| 22 | FileUtils.mkdir_p(File.dirname(file)) unless File.exist?(file) 23 | File.open(file, 'w') do |f| 24 | f.write(text.gsub(/\\xFF/, 0xFF.chr)) 25 | end 26 | end 27 | 28 | When(%r{^I run bin/texqc with "([^"]*)"$}) do |arg| 29 | home = File.join(File.dirname(__FILE__), '../..') 30 | @stdout = `ruby -I#{home}/lib #{home}/bin/texqc #{arg}` 31 | @exitstatus = $CHILD_STATUS.exitstatus 32 | end 33 | 34 | Then(/^Stdout contains "([^"]*)"$/) do |txt| 35 | raise "STDOUT doesn't contain '#{txt}':\n#{@stdout}" unless @stdout.include?(txt) 36 | end 37 | 38 | Then(/^Stdout is empty$/) do 39 | raise "STDOUT is not empty:\n#{@stdout}" unless @stdout == '' 40 | end 41 | 42 | Then(/^Exit code is zero$/) do 43 | raise "Non-zero exit #{@exitstatus}:\n#{@stdout}" unless @exitstatus.zero? 44 | end 45 | 46 | Then(/^Exit code is not zero$/) do 47 | raise 'Zero exit code' if @exitstatus.zero? 48 | end 49 | 50 | When(/^I run bash with "([^"]*)"$/) do |text| 51 | FileUtils.copy_entry(@cwd, File.join(@dir, 'texqc')) 52 | @stdout = `#{text}` 53 | @exitstatus = $CHILD_STATUS.exitstatus 54 | end 55 | 56 | When(/^I run bash with:$/) do |text| 57 | FileUtils.copy_entry(@cwd, File.join(@dir, 'texqc')) 58 | @stdout = `#{text}` 59 | @exitstatus = $CHILD_STATUS.exitstatus 60 | end 61 | 62 | Given(/^It is Unix$/) do 63 | pending if Gem.win_platform? 64 | end 65 | 66 | Given(/^It is Windows$/) do 67 | pending unless Gem.win_platform? 68 | end 69 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'simplecov' 7 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group 2 4 | 5 | 6 | 7 | 8 | 9 | texqc 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /texqc.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'English' 7 | 8 | lib = File.expand_path('lib', __dir__) 9 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 10 | Gem::Specification.new do |s| 11 | s.specification_version = 2 if s.respond_to? :specification_version= 12 | if s.respond_to? :required_rubygems_version= 13 | s.required_rubygems_version = Gem::Requirement.new('>= 0') 14 | end 15 | s.rubygems_version = '2.2' 16 | s.required_ruby_version = '>= 2.3' 17 | s.name = 'texqc' 18 | s.version = '0.0.0' 19 | s.license = 'MIT' 20 | s.summary = 'Quality Control of Your LaTeX Build' 21 | s.description = 'Run it after you compile your LaTeX document' 22 | s.authors = ['Yegor Bugayenko'] 23 | s.email = 'yegor256@gmail.com' 24 | s.homepage = 'http://github.com/yegor256/texqc' 25 | s.files = `git ls-files`.split($RS) 26 | s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } 27 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 28 | s.rdoc_options = ['--charset=UTF-8'] 29 | s.extra_rdoc_files = ['README.md', 'LICENSE.txt'] 30 | s.add_runtime_dependency 'backtrace', '~> 0.3' 31 | s.add_runtime_dependency 'loog', '~> 0.2' 32 | s.add_runtime_dependency 'slop', '~> 4.8.2' 33 | s.add_development_dependency 'codecov', '0.2.6' 34 | s.add_development_dependency 'cucumber', '~> 1.3.17' 35 | s.add_development_dependency 'rake', '12.3.3' 36 | s.add_development_dependency 'rubocop', '0.61.0' 37 | s.add_development_dependency 'rubocop-rspec', '1.31.0' 38 | end 39 | --------------------------------------------------------------------------------