├── .circleci └── config.yml ├── .config └── rubocop │ └── config.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .reek.yml ├── .ruby-version ├── CITATION.cff ├── Gemfile ├── LICENSE.adoc ├── README.adoc ├── Rakefile ├── bin ├── console ├── rake ├── rspec ├── rubocop └── setup ├── exe └── pragmater ├── lib ├── pragmater.rb └── pragmater │ ├── cli │ ├── actions │ │ ├── comment.rb │ │ ├── pattern.rb │ │ └── root.rb │ ├── commands │ │ ├── insert.rb │ │ └── remove.rb │ └── shell.rb │ ├── configuration │ ├── contract.rb │ ├── defaults.yml │ └── model.rb │ ├── container.rb │ ├── dependencies.rb │ ├── formatters │ ├── general.rb │ ├── main.rb │ └── shebang.rb │ ├── inserter.rb │ ├── parsers │ ├── comments.rb │ └── file.rb │ ├── processors │ ├── handler.rb │ ├── inserter.rb │ └── remover.rb │ └── remover.rb ├── pragmater.gemspec └── spec ├── lib ├── pragmater │ ├── cli │ │ ├── actions │ │ │ ├── comment_spec.rb │ │ │ ├── pattern_spec.rb │ │ │ └── root_spec.rb │ │ ├── commands │ │ │ ├── insert_spec.rb │ │ │ └── remove_spec.rb │ │ └── shell_spec.rb │ ├── configuration │ │ └── model_spec.rb │ ├── formatters │ │ ├── general_spec.rb │ │ ├── main_spec.rb │ │ └── shebang_spec.rb │ ├── inserter_spec.rb │ ├── parsers │ │ ├── comments_spec.rb │ │ └── file_spec.rb │ ├── processors │ │ ├── handler_spec.rb │ │ ├── inserter_spec.rb │ │ └── remover_spec.rb │ └── remover_spec.rb └── pragmater_spec.rb ├── spec_helper.rb └── support ├── fixtures ├── with_comments.rb └── with_comments_and_spacing.rb └── shared_contexts ├── application_dependencies.rb └── temp_dir.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | working_directory: ~/project 5 | docker: 6 | - image: bkuhlmann/alpine-ruby:latest 7 | steps: 8 | - checkout 9 | 10 | - restore_cache: 11 | name: Gems Restore 12 | keys: 13 | - gem-cache-{{.Branch}}-{{checksum "Gemfile"}}-{{checksum "pragmater.gemspec"}} 14 | - gem-cache- 15 | 16 | - run: 17 | name: Gems Install 18 | command: | 19 | gem update --system 20 | bundle config set path "vendor/bundle" 21 | bundle install 22 | 23 | - save_cache: 24 | name: Gems Store 25 | key: gem-cache-{{.Branch}}-{{checksum "Gemfile"}}-{{checksum "pragmater.gemspec"}} 26 | paths: 27 | - vendor/bundle 28 | 29 | - run: 30 | name: Rake 31 | command: bundle exec rake 32 | 33 | - store_artifacts: 34 | name: SimpleCov Report 35 | path: ~/project/coverage 36 | destination: coverage 37 | -------------------------------------------------------------------------------- /.config/rubocop/config.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | caliber: config/all.yml 3 | 4 | Style/FrozenStringLiteralComment: 5 | Exclude: 6 | - spec/support/fixtures/**/* 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [bkuhlmann] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Why 2 | 3 | 4 | ## How 5 | 6 | 7 | ## Notes 8 | 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | 4 | ## Screenshots/Screencasts 5 | 6 | 7 | ## Details 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg 5 | tmp 6 | -------------------------------------------------------------------------------- /.reek.yml: -------------------------------------------------------------------------------- 1 | exclude_paths: 2 | - tmp 3 | - vendor 4 | 5 | detectors: 6 | LongParameterList: 7 | enabled: false 8 | TooManyStatements: 9 | exclude: 10 | - "Pragmater::CLI::Shell#cli" 11 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.4 2 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: Please use the following metadata when citing this project in your work. 3 | title: Pragmater 4 | abstract: A command line interface for managing pragma comments. 5 | version: 16.2.1 6 | license: Hippocratic-2.1 7 | date-released: 2025-05-21 8 | authors: 9 | - family-names: Kuhlmann 10 | given-names: Brooke 11 | affiliation: Alchemists 12 | orcid: https://orcid.org/0000-0002-5810-6268 13 | keywords: 14 | - ruby 15 | - command line interface 16 | - pragmas 17 | - script headers 18 | - formatter 19 | - comments 20 | repository-code: https://github.com/bkuhlmann/pragmater 21 | repository-artifact: https://rubygems.org/gems/pragmater 22 | url: https://alchemists.io/projects/pragmater 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ruby file: ".ruby-version" 4 | 5 | source "https://rubygems.org" 6 | 7 | gemspec 8 | 9 | group :quality do 10 | gem "caliber", "~> 0.79" 11 | gem "git-lint", "~> 9.0" 12 | gem "reek", "~> 6.5", require: false 13 | gem "simplecov", "~> 0.22", require: false 14 | end 15 | 16 | group :development do 17 | gem "rake", "~> 13.2" 18 | end 19 | 20 | group :test do 21 | gem "rspec", "~> 3.13" 22 | end 23 | 24 | group :tools do 25 | gem "amazing_print", "~> 1.8" 26 | gem "debug", "~> 1.10" 27 | gem "irb-kit", "~> 1.1" 28 | gem "repl_type_completor", "~> 0.1" 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE.adoc: -------------------------------------------------------------------------------- 1 | = Hippocratic License 2 | 3 | Version: 2.1.0. 4 | 5 | Purpose. The purpose of this License is for the Licensor named above to 6 | permit the Licensee (as defined below) broad permission, if consistent 7 | with Human Rights Laws and Human Rights Principles (as each is defined 8 | below), to use and work with the Software (as defined below) within the 9 | full scope of Licensor’s copyright and patent rights, if any, in the 10 | Software, while ensuring attribution and protecting the Licensor from 11 | liability. 12 | 13 | Permission and Conditions. The Licensor grants permission by this 14 | license ("License"), free of charge, to the extent of Licensor’s 15 | rights under applicable copyright and patent law, to any person or 16 | entity (the "Licensee") obtaining a copy of this software and 17 | associated documentation files (the "Software"), to do everything with 18 | the Software that would otherwise infringe (i) the Licensor’s copyright 19 | in the Software or (ii) any patent claims to the Software that the 20 | Licensor can license or becomes able to license, subject to all of the 21 | following terms and conditions: 22 | 23 | * Acceptance. This License is automatically offered to every person and 24 | entity subject to its terms and conditions. Licensee accepts this 25 | License and agrees to its terms and conditions by taking any action with 26 | the Software that, absent this License, would infringe any intellectual 27 | property right held by Licensor. 28 | * Notice. Licensee must ensure that everyone who gets a copy of any part 29 | of this Software from Licensee, with or without changes, also receives 30 | the License and the above copyright notice (and if included by the 31 | Licensor, patent, trademark and attribution notice). Licensee must cause 32 | any modified versions of the Software to carry prominent notices stating 33 | that Licensee changed the Software. For clarity, although Licensee is 34 | free to create modifications of the Software and distribute only the 35 | modified portion created by Licensee with additional or different terms, 36 | the portion of the Software not modified must be distributed pursuant to 37 | this License. If anyone notifies Licensee in writing that Licensee has 38 | not complied with this Notice section, Licensee can keep this License by 39 | taking all practical steps to comply within 30 days after the notice. If 40 | Licensee does not do so, Licensee’s License (and all rights licensed 41 | hereunder) shall end immediately. 42 | * Compliance with Human Rights Principles and Human Rights Laws. 43 | [arabic] 44 | . Human Rights Principles. 45 | [loweralpha] 46 | .. Licensee is advised to consult the articles of the United Nations 47 | Universal Declaration of Human Rights and the United Nations Global 48 | Compact that define recognized principles of international human rights 49 | (the "Human Rights Principles"). Licensee shall use the Software in a 50 | manner consistent with Human Rights Principles. 51 | .. Unless the Licensor and Licensee agree otherwise, any dispute, 52 | controversy, or claim arising out of or relating to (i) Section 1(a) 53 | regarding Human Rights Principles, including the breach of Section 1(a), 54 | termination of this License for breach of the Human Rights Principles, 55 | or invalidity of Section 1(a) or (ii) a determination of whether any Law 56 | is consistent or in conflict with Human Rights Principles pursuant to 57 | Section 2, below, shall be settled by arbitration in accordance with the 58 | Hague Rules on Business and Human Rights Arbitration (the "Rules"); 59 | provided, however, that Licensee may elect not to participate in such 60 | arbitration, in which event this License (and all rights licensed 61 | hereunder) shall end immediately. The number of arbitrators shall be one 62 | unless the Rules require otherwise. 63 | + 64 | Unless both the Licensor and Licensee agree to the contrary: (1) All 65 | documents and information concerning the arbitration shall be public and 66 | may be disclosed by any party; (2) The repository referred to under 67 | Article 43 of the Rules shall make available to the public in a timely 68 | manner all documents concerning the arbitration which are communicated 69 | to it, including all submissions of the parties, all evidence admitted 70 | into the record of the proceedings, all transcripts or other recordings 71 | of hearings and all orders, decisions and awards of the arbitral 72 | tribunal, subject only to the arbitral tribunal’s powers to take such 73 | measures as may be necessary to safeguard the integrity of the arbitral 74 | process pursuant to Articles 18, 33, 41 and 42 of the Rules; and (3) 75 | Article 26(6) of the Rules shall not apply. 76 | . Human Rights Laws. The Software shall not be used by any person or 77 | entity for any systems, activities, or other uses that violate any Human 78 | Rights Laws. "Human Rights Laws" means any applicable laws, 79 | regulations, or rules (collectively, "Laws") that protect human, 80 | civil, labor, privacy, political, environmental, security, economic, due 81 | process, or similar rights; provided, however, that such Laws are 82 | consistent and not in conflict with Human Rights Principles (a dispute 83 | over the consistency or a conflict between Laws and Human Rights 84 | Principles shall be determined by arbitration as stated above). Where 85 | the Human Rights Laws of more than one jurisdiction are applicable or in 86 | conflict with respect to the use of the Software, the Human Rights Laws 87 | that are most protective of the individuals or groups harmed shall 88 | apply. 89 | . Indemnity. Licensee shall hold harmless and indemnify Licensor (and 90 | any other contributor) against all losses, damages, liabilities, 91 | deficiencies, claims, actions, judgments, settlements, interest, awards, 92 | penalties, fines, costs, or expenses of whatever kind, including 93 | Licensor’s reasonable attorneys’ fees, arising out of or relating to 94 | Licensee’s use of the Software in violation of Human Rights Laws or 95 | Human Rights Principles. 96 | * Failure to Comply. Any failure of Licensee to act according to the 97 | terms and conditions of this License is both a breach of the License and 98 | an infringement of the intellectual property rights of the Licensor 99 | (subject to exceptions under Laws, e.g., fair use). In the event of a 100 | breach or infringement, the terms and conditions of this License may be 101 | enforced by Licensor under the Laws of any jurisdiction to which 102 | Licensee is subject. Licensee also agrees that the Licensor may enforce 103 | the terms and conditions of this License against Licensee through 104 | specific performance (or similar remedy under Laws) to the extent 105 | permitted by Laws. For clarity, except in the event of a breach of this 106 | License, infringement, or as otherwise stated in this License, Licensor 107 | may not terminate this License with Licensee. 108 | * Enforceability and Interpretation. If any term or provision of this 109 | License is determined to be invalid, illegal, or unenforceable by a 110 | court of competent jurisdiction, then such invalidity, illegality, or 111 | unenforceability shall not affect any other term or provision of this 112 | License or invalidate or render unenforceable such term or provision in 113 | any other jurisdiction; provided, however, subject to a court 114 | modification pursuant to the immediately following sentence, if any term 115 | or provision of this License pertaining to Human Rights Laws or Human 116 | Rights Principles is deemed invalid, illegal, or unenforceable against 117 | Licensee by a court of competent jurisdiction, all rights in the 118 | Software granted to Licensee shall be deemed null and void as between 119 | Licensor and Licensee. Upon a determination that any term or provision 120 | is invalid, illegal, or unenforceable, to the extent permitted by Laws, 121 | the court may modify this License to affect the original purpose that 122 | the Software be used in compliance with Human Rights Principles and 123 | Human Rights Laws as closely as possible. The language in this License 124 | shall be interpreted as to its fair meaning and not strictly for or 125 | against any party. 126 | * Disclaimer. TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES 127 | "AS IS," WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR AND 128 | ANY OTHER CONTRIBUTOR SHALL NOT BE LIABLE TO ANYONE FOR ANY DAMAGES OR 129 | OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE 130 | OR THIS LICENSE, UNDER ANY KIND OF LEGAL CLAIM. 131 | 132 | This Hippocratic License is an link:https://ethicalsource.dev[Ethical Source license] and is offered 133 | for use by licensors and licensees at their own risk, on an "AS IS" basis, and with no warranties 134 | express or implied, to the maximum extent permitted by Laws. 135 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | :toc: macro 2 | :toclevels: 5 3 | :figure-caption!: 4 | 5 | = Pragmater 6 | 7 | A command line interface that does one thing well by being entirely focused on managing/formatting 8 | source file https://en.wikipedia.org/wiki/Directive_(programming)[directive pragmas] (a.k.a. _magic 9 | comments_). Examples: 10 | 11 | [source,ruby] 12 | ---- 13 | #! /usr/bin/env ruby 14 | # frozen_string_literal: true 15 | # encoding: UTF-8 16 | ---- 17 | 18 | With https://www.ruby-lang.org/en/news/2015/12/25/ruby-2-3-0-released[Ruby 2.3.0], frozen strings 19 | are supported via a pragma. This gem provides an easy way to insert or remove pragmas to single or 20 | multiple Ruby source files in order to benefit from improved memory and concurrency performance. 21 | 22 | toc::[] 23 | 24 | == Features 25 | 26 | * Supports inserting a pragma or multiple pragmas to single or multiple source files. 27 | * Supports removing pragma(s) from single or multiple source files. 28 | * Supports file list filtering. Defaults to any file. 29 | * Ensures duplicate pragmas never exist. 30 | * Ensures pragmas are consistently formatted. 31 | 32 | == Requirements 33 | 34 | . https://www.ruby-lang.org[Ruby] 35 | 36 | == Setup 37 | 38 | To install _with_ security, run: 39 | 40 | [source,bash] 41 | ---- 42 | # 💡 Skip this line if you already have the public certificate installed. 43 | gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem) 44 | gem install pragmater --trust-policy HighSecurity 45 | ---- 46 | 47 | To install _without_ security, run: 48 | 49 | [source,bash] 50 | ---- 51 | gem install pragmater 52 | ---- 53 | 54 | == Usage 55 | 56 | === Command Line Interface (CLI) 57 | 58 | From the command line, type: `pragmater --help` 59 | 60 | image:https://alchemists.io/images/projects/pragmater/screenshots/usage.png[Usage,width=581,height=345,role=focal_point] 61 | 62 | Both the `insert` and `remove` commands support the same options for specifying pragmas and/or 63 | included files. Example: 64 | 65 | [source,bash] 66 | ---- 67 | pragmater insert --comments "# frozen_string_literal: true" --patterns "Gemfile" "Guardfile" "Rakefile" ".gemspec" "config.ru" "bin/**/*" "**/*.rake" "**/*.rb" 68 | ---- 69 | 70 | The `insert` and `remove` commands default to the current working directory so a path isn’t 71 | necessary unless you want to run Pragmater in a directory structure other than your current working 72 | directory. 73 | 74 | === Customization 75 | 76 | This gem can be configured via a global configuration: `$HOME/.config/pragmater/configuration.yml` 77 | 78 | It can also be configured via link:https://alchemists.io/projects/xdg[XDG] environment 79 | variables. 80 | 81 | The default configuration is as follows: 82 | 83 | [source,yaml] 84 | ---- 85 | comments: [] 86 | patterns: [] 87 | root_dir: "." 88 | ---- 89 | 90 | Feel free to take the above configuration, modify, and save as your own custom `configuration.yml`. 91 | 92 | The `configuration.yml` file can be configured as follows: 93 | 94 | * `comments`: Defines the array of pragmas you want to insert into your source files. Whatever is 95 | defined here will be the default used for insert and remove operations. 96 | * `includes`: Defines the array file patterns to apply to. Whatever is defined here will be the 97 | default used for insert and remove operations. 98 | * `root_dir`: Defines the root directory to apply the `includes` patterns too. By default, this will 99 | be the current directory you are running Pragmater from but can be a different directory entirely. 100 | 101 | === Available Pragmas 102 | 103 | With Ruby 2.3 and higher, the following pragmas are available: 104 | 105 | * `# encoding:` Defaults to `UTF-8` but any supported encoding can be used. For a list of values, 106 | launch an IRB session and run `Encoding.name_list`. 107 | * `# coding:` The shorthand for `# encoding:`. Supports the same values as mentioned above. 108 | * `# frozen_string_literal:` Defaults to `false` but can take either `true` or `false` as a value. 109 | When enabled, Ruby will throw errors when strings are used in a mutable fashion. 110 | * `# warn_indent:` Defaults to `false` but can take either `true` or `false` as a value. When 111 | enabled, and running Ruby with the `-w` option, it’ll throw warnings for code that isn’t indented 112 | by two spaces. 113 | 114 | === Syntax 115 | 116 | The pragma syntax allows for two kinds of styles. Example: 117 | 118 | [source,ruby] 119 | ---- 120 | # encoding: UTF-8 121 | # -*- encoding: UTF-8 -*- 122 | ---- 123 | 124 | Only the former syntax is supported by this gem as the latter syntax is more verbose and requires 125 | additional typing. 126 | 127 | === Precedence 128 | 129 | When different multiple pragmas are defined, they all take precedence: 130 | 131 | [source,ruby] 132 | ---- 133 | # encoding: binary 134 | # frozen_string_literal: true 135 | ---- 136 | 137 | In the above example, both _binary_ encoding and _frozen string literals_ behavior will be applied. 138 | 139 | When defining multiple pragmas that are similar, behavior can differ based on the _kind_ of pragma 140 | used. The following walks through each use case so you know what to expect: 141 | 142 | [source,ruby] 143 | ---- 144 | # encoding: binary 145 | # encoding: UTF-8 146 | ---- 147 | 148 | In the above example, only the _binary_ encoding will be applied while the _UTF-8_ encoding will be 149 | ignored (same principle applies for the `coding` pragma too). 150 | 151 | [source,ruby] 152 | ---- 153 | # frozen_string_literal: false 154 | # frozen_string_literal: true 155 | ---- 156 | 157 | In the above example, frozen string literal support _will be enabled_ instead of being disabled. 158 | 159 | [source,ruby] 160 | ---- 161 | # warn_indent: false 162 | # warn_indent: true 163 | ---- 164 | 165 | In the above example, indentation warnings _will be enabled_ instead of being disabled. 166 | 167 | === Frozen String Literals 168 | 169 | Support for frozen string literals was added in Ruby 2.3.0. The ability to freeze strings within a 170 | source can be done by placing a frozen string pragma at the top of each source file. Example: 171 | 172 | [source,ruby] 173 | ---- 174 | # frozen_string_literal: true 175 | ---- 176 | 177 | This is great for _selective_ enablement of frozen string literals but might be too much work for 178 | some (even with the aid of this gem). As an alternative, frozen string literals can be enabled via 179 | the following Ruby command line option: 180 | 181 | .... 182 | --enable=frozen-string-literal 183 | .... 184 | 185 | It is important to note that, once enabled, this freezes strings program-wide – It’s an all or 186 | nothing option. 187 | 188 | Regardless of whether you leverage the capabilities of this gem or the Ruby command line option 189 | mentioned above, the following Ruby command line option is available to aid debugging and tracking 190 | down frozen string literal issues: 191 | 192 | .... 193 | --debug=frozen-string-literal 194 | .... 195 | 196 | Finally, you can use `--debug` (or `$DEBUG=true`) to force all raised exceptions to print to the console whether they are rescued or not. This is best used in conjunction with the above. 197 | 198 | Ruby 2.3.0 also added the following methods to the `String` class: 199 | 200 | * `String#+@`: Answers a duplicated, mutable, string if not already frozen. Example: 201 | + 202 | [source,ruby] 203 | ---- 204 | immutable = "test".freeze 205 | mutable = +immutable 206 | 207 | mutable.frozen? # false 208 | mutable.capitalize! # "Test" 209 | ---- 210 | * `String#-@`: Answers a immutable string if not already frozen. Example: 211 | + 212 | [source,ruby] 213 | ---- 214 | mutable = "test" 215 | immutable = -mutable 216 | 217 | immutable.frozen? # true 218 | immutable.capitalize! # FrozenError 219 | ---- 220 | 221 | You can also use the methods, shown above, for variable initialization. Example: 222 | 223 | [source,ruby] 224 | ---- 225 | immutable = -"test" 226 | mutable = +"test" 227 | 228 | immutable.frozen? # true 229 | mutable.frozen? # false 230 | ---- 231 | 232 | 💡 Use of `+String#-@+` was link:https://bugs.ruby-lang.org/issues/13077[enhanced in Ruby 2.5.0] to 233 | _deduplicate_ all instances of the same string thus reducing your memory footprint. This can be 234 | valuable in situations where you are not using the frozen string comment and need to selectively 235 | freeze strings. 236 | 237 | 💡 Use of `+String#dup+` was link:https://github.com/ruby/ruby/pull/8952[significantly enhanced in Ruby 3.3.0] to be as performant as `pass:[String#+@]` so you can use `+String#dup+` instead of `pass:[String#+@]` since `+String#dup+` is easier to read. 238 | 239 | === Consistency 240 | 241 | As an added bonus, this gem ensures pragmas for all analyzed files are formatted in a consistent 242 | style. This means there is always a space after the octothorp (`#`). Here are multiple pragmas 243 | presented together for a visual comparison: 244 | 245 | [source,ruby] 246 | ---- 247 | #! /usr/bin/env ruby 248 | # encoding: UTF-8 249 | # coding: UTF-8 250 | # frozen_string_literal: true 251 | # warn_indent: true 252 | ---- 253 | 254 | One oddity to the above is the use of `# !/usr/bin/env ruby` is not allowed but `#! /usr/bin/env 255 | ruby` is which is why spacing is slightly different for shell pragmas. 256 | 257 | == Development 258 | 259 | To contribute, run: 260 | 261 | [source,bash] 262 | ---- 263 | git clone https://github.com/bkuhlmann/pragmater 264 | cd pragmater 265 | bin/setup 266 | ---- 267 | 268 | You can also use the IRB console for direct access to all objects: 269 | 270 | [source,bash] 271 | ---- 272 | bin/console 273 | ---- 274 | 275 | == Tests 276 | 277 | To test, run: 278 | 279 | [source,bash] 280 | ---- 281 | bin/rake 282 | ---- 283 | 284 | == link:https://alchemists.io/policies/license[License] 285 | 286 | == link:https://alchemists.io/policies/security[Security] 287 | 288 | == link:https://alchemists.io/policies/code_of_conduct[Code of Conduct] 289 | 290 | == link:https://alchemists.io/policies/contributions[Contributions] 291 | 292 | == link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin] 293 | 294 | == link:https://alchemists.io/projects/pragmater/versions[Versions] 295 | 296 | == link:https://alchemists.io/community[Community] 297 | 298 | == Credits 299 | 300 | * Built with link:https://alchemists.io/projects/gemsmith[Gemsmith]. 301 | * Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann]. 302 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/setup" 4 | require "git/lint/rake/register" 5 | require "reek/rake/task" 6 | require "rspec/core/rake_task" 7 | require "rubocop/rake_task" 8 | 9 | Git::Lint::Rake::Register.call 10 | Reek::Rake::Task.new 11 | RSpec::Core::RakeTask.new { |task| task.verbose = false } 12 | RuboCop::RakeTask.new 13 | 14 | desc "Run code quality checks" 15 | task quality: %i[git_lint reek rubocop] 16 | 17 | task default: %i[quality spec] 18 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | Bundler.require :tools 6 | 7 | require "irb" 8 | require "pragmater" 9 | 10 | IRB.start __FILE__ 11 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | 6 | load Gem.bin_path "rake", "rake" 7 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | 6 | load Gem.bin_path "rspec-core", "rspec" 7 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | 6 | load Gem.bin_path "rubocop", "rubocop" 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "debug" 5 | require "fileutils" 6 | require "pathname" 7 | 8 | APP_ROOT = Pathname(__dir__).join("..").expand_path 9 | 10 | Runner = lambda do |*arguments, kernel: Kernel| 11 | kernel.system(*arguments) || kernel.abort("\nERROR: Command #{arguments.inspect} failed.") 12 | end 13 | 14 | FileUtils.chdir APP_ROOT do 15 | puts "Installing dependencies..." 16 | Runner.call "bundle install" 17 | end 18 | -------------------------------------------------------------------------------- /exe/pragmater: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "pragmater" 5 | 6 | Pragmater::CLI::Shell.new.call 7 | -------------------------------------------------------------------------------- /lib/pragmater.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "zeitwerk" 4 | 5 | Zeitwerk::Loader.new.then do |loader| 6 | loader.inflector.inflect "cli" => "CLI" 7 | loader.tag = File.basename __FILE__, ".rb" 8 | loader.push_dir __dir__ 9 | loader.setup 10 | end 11 | 12 | # Main namespace. 13 | module Pragmater 14 | def self.loader registry = Zeitwerk::Registry 15 | @loader ||= registry.loaders.each.find { |loader| loader.tag == File.basename(__FILE__, ".rb") } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pragmater/cli/actions/comment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sod" 4 | 5 | module Pragmater 6 | module CLI 7 | module Actions 8 | # Stores pragma comments. 9 | class Comment < Sod::Action 10 | include Dependencies[:settings] 11 | 12 | description "Set pragma comments." 13 | 14 | on %w[-c --comments], argument: "[a,b,c]" 15 | 16 | default { Container[:settings].comments } 17 | 18 | def call(comments = default) = settings.comments = Array(comments) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pragmater/cli/actions/pattern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sod" 4 | 5 | module Pragmater 6 | module CLI 7 | module Actions 8 | # Stores file patterns. 9 | class Pattern < Sod::Action 10 | include Dependencies[:settings] 11 | 12 | description "Set file patterns." 13 | 14 | on %w[-p --patterns], argument: "[a,b,c]" 15 | 16 | default { Container[:settings].patterns } 17 | 18 | def call(patterns = default) = settings.patterns = Array(patterns) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pragmater/cli/actions/root.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sod" 4 | 5 | module Pragmater 6 | module CLI 7 | module Actions 8 | # Stores root path. 9 | class Root < Sod::Action 10 | include Dependencies[:settings] 11 | 12 | description "Set root directory." 13 | 14 | on %w[-r --root], argument: "[PATH]" 15 | 16 | default { Container[:settings].root_dir } 17 | 18 | def call(path = default) = settings.root_dir = Pathname(path) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pragmater/cli/commands/insert.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sod" 4 | 5 | module Pragmater 6 | module CLI 7 | module Commands 8 | # Inserts pragmas. 9 | class Insert < Sod::Command 10 | include Dependencies[:settings, :io] 11 | 12 | handle "insert" 13 | 14 | description "Insert pragma comments." 15 | 16 | on Actions::Root 17 | on Actions::Comment 18 | on Actions::Pattern 19 | 20 | def initialize(handler: Inserter.new, **) 21 | super(**) 22 | @handler = handler 23 | end 24 | 25 | def call = handler.call { |path| io.puts path } 26 | 27 | private 28 | 29 | attr_reader :handler 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/pragmater/cli/commands/remove.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sod" 4 | 5 | module Pragmater 6 | module CLI 7 | module Commands 8 | # Removes pragmas. 9 | class Remove < Sod::Command 10 | include Dependencies[:settings, :io] 11 | 12 | handle "remove" 13 | 14 | description "Remove pragma comments." 15 | 16 | on Actions::Root 17 | on Actions::Comment 18 | on Actions::Pattern 19 | 20 | def initialize(handler: Remover.new, **) 21 | super(**) 22 | @handler = handler 23 | end 24 | 25 | def call = handler.call { |path| io.puts path } 26 | 27 | private 28 | 29 | attr_reader :handler 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/pragmater/cli/shell.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sod" 4 | 5 | module Pragmater 6 | module CLI 7 | # The main Command Line Interface (CLI) object. 8 | class Shell 9 | include Dependencies[:defaults_path, :xdg_config, :specification] 10 | 11 | def initialize(context: Sod::Context, dsl: Sod, **) 12 | super(**) 13 | @context = context 14 | @dsl = dsl 15 | end 16 | 17 | def call(...) = cli.call(...) 18 | 19 | private 20 | 21 | attr_reader :context, :dsl 22 | 23 | def cli 24 | context = build_context 25 | 26 | dsl.new :pragmater, banner: specification.banner do 27 | on(Sod::Prefabs::Commands::Config, context:) 28 | on Commands::Insert 29 | on Commands::Remove 30 | on(Sod::Prefabs::Actions::Version, context:) 31 | on Sod::Prefabs::Actions::Help, self 32 | end 33 | end 34 | 35 | def build_context 36 | context[defaults_path:, xdg_config:, version_label: specification.labeled_version] 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/pragmater/configuration/contract.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "dry/schema" 4 | require "etcher" 5 | 6 | Dry::Schema.load_extensions :monads 7 | 8 | module Pragmater 9 | module Configuration 10 | Contract = Dry::Schema.Params do 11 | required(:comments).array :string 12 | required(:patterns).array :string 13 | required(:root_dir).filled Etcher::Types::Pathname 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pragmater/configuration/defaults.yml: -------------------------------------------------------------------------------- 1 | comments: [] 2 | patterns: [] 3 | root_dir: "." 4 | -------------------------------------------------------------------------------- /lib/pragmater/configuration/model.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Configuration 5 | # Defines the content of the configuration for use throughout the gem. 6 | Model = Struct.new :comments, :patterns, :root_dir do 7 | def initialize(**) 8 | super 9 | self[:comments] = Array comments 10 | self[:patterns] = Array patterns 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/pragmater/container.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "cogger" 4 | require "containable" 5 | require "etcher" 6 | require "runcom" 7 | require "spek" 8 | 9 | module Pragmater 10 | # Provides a global gem container for injection into other objects. 11 | module Container 12 | extend Containable 13 | 14 | register :registry, as: :fresh do 15 | Etcher::Registry.new(contract: Configuration::Contract, model: Configuration::Model) 16 | .add_loader(:yaml, self[:defaults_path]) 17 | .add_loader(:yaml, self[:xdg_config].active) 18 | end 19 | 20 | register(:settings) { Etcher.call(self[:registry]).dup } 21 | register(:specification) { Spek::Loader.call "#{__dir__}/../../pragmater.gemspec" } 22 | register(:defaults_path) { Pathname(__dir__).join("configuration/defaults.yml") } 23 | register(:xdg_config) { Runcom::Config.new "pragmater/configuration.yml" } 24 | register(:logger) { Cogger.new id: :pragmater } 25 | register :io, STDOUT 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pragmater/dependencies.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "infusible" 4 | 5 | module Pragmater 6 | Dependencies = Infusible[Container] 7 | end 8 | -------------------------------------------------------------------------------- /lib/pragmater/formatters/general.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Formatters 5 | # Formats general pragmas in a consistent manner. 6 | class General 7 | PATTERN = / 8 | \A # Start of line. 9 | \# # Start of comment. 10 | \s? # Space - optional. 11 | \w+ # Key - One or more word characters only. 12 | : # Delimiter. 13 | \s? # Space - optional. 14 | [\w-]+ # Value - One or more word or dash characters. 15 | \Z # End of line. 16 | /x 17 | 18 | def initialize string, pattern: PATTERN 19 | @string = string 20 | @pattern = pattern 21 | end 22 | 23 | def call 24 | return string unless string.match? pattern 25 | 26 | string.split(":").then { |key, value| "# #{key.gsub(/\#\s?/, "")}: #{value.strip}" } 27 | end 28 | 29 | private 30 | 31 | attr_reader :string, :pattern 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/pragmater/formatters/main.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Formatters 5 | # Formats all pragmas in a consistent manner. 6 | class Main 7 | FORMATTERS = [General, Shebang].freeze 8 | 9 | PATTERN = FORMATTERS.map { |formatter| formatter::PATTERN } 10 | .then { |patterns| Regexp.union(*patterns) } 11 | .freeze 12 | 13 | def initialize string, formatters: FORMATTERS 14 | @string = string 15 | @formatters = formatters 16 | end 17 | 18 | def call = formatters.reduce(string) { |pragma, formatter| formatter.new(pragma).call } 19 | 20 | private 21 | 22 | attr_reader :string, :formatters 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pragmater/formatters/shebang.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Formatters 5 | # Formats shebang pragmas in a consistent manner. 6 | class Shebang 7 | PATTERN = %r(\A\#!\s?/.*ruby\Z) 8 | 9 | def initialize string, pattern: PATTERN 10 | @string = string 11 | @pattern = pattern 12 | end 13 | 14 | def call 15 | return string unless string.match? pattern 16 | 17 | string.split("!").then { |octothorpe, path| "#{octothorpe}! #{path.strip}" } 18 | end 19 | 20 | private 21 | 22 | attr_reader :string, :pattern 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pragmater/inserter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "refinements/pathname" 4 | 5 | module Pragmater 6 | # Inserts pragma comments. 7 | class Inserter 8 | include Dependencies[:settings] 9 | 10 | using Refinements::Pathname 11 | 12 | def initialize(parser: Parsers::File.new, **) 13 | @parser = parser 14 | super(**) 15 | end 16 | 17 | def call 18 | Pathname(settings.root_dir).files("{#{settings.patterns.join ","}}").map do |path| 19 | yield path if block_given? 20 | path.write parser.call(path, settings.comments, action: :insert).join 21 | end 22 | end 23 | 24 | private 25 | 26 | attr_reader :parser 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pragmater/parsers/comments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Parsers 5 | # Manages pragma comments. 6 | class Comments 7 | def initialize older, newer, formatter: Formatters::Main 8 | @formatter = formatter 9 | @older = format older 10 | @newer = format newer 11 | end 12 | 13 | def insert = older.union(newer) 14 | 15 | def remove = older - older.intersection(newer) 16 | 17 | private 18 | 19 | attr_reader :formatter, :older, :newer 20 | 21 | def format(pragmas) = Array(pragmas).map { |pragma| formatter.new(pragma).call } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pragmater/parsers/file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Parsers 5 | # Parses a file into pragma comment and body lines. 6 | class File 7 | def initialize pattern: Formatters::Main::PATTERN, 8 | comments: Comments, 9 | processor: Processors::Handler.new 10 | @pattern = pattern 11 | @comments = comments 12 | @processor = processor 13 | end 14 | 15 | def call path, new_comments, action: 16 | path.each_line 17 | .partition { |line| line.match? pattern } 18 | .then do |old_comments, body| 19 | processor.call action, wrap_in_new_line(old_comments, new_comments, action), body 20 | end 21 | end 22 | 23 | private 24 | 25 | attr_reader :pattern, :comments, :processor 26 | 27 | def wrap_in_new_line old_comments, new_comments, action 28 | comments.new(old_comments, new_comments).public_send(action).map { |line| "#{line}\n" } 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/pragmater/processors/handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Processors 5 | # Handles the insertion or removal of pragma comments. 6 | class Handler 7 | DEFAULTS = {insert: Inserter, remove: Remover}.freeze 8 | 9 | def initialize processors: DEFAULTS 10 | @processors = processors 11 | end 12 | 13 | def call(action, comments, body) = processors.fetch(action).new(comments, body).call 14 | 15 | private 16 | 17 | attr_reader :processors 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pragmater/processors/inserter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Processors 5 | # Inserts new pragma comments. 6 | class Inserter 7 | def initialize comments, body 8 | @comments = comments 9 | @body = body 10 | end 11 | 12 | def call 13 | body.first.then do |first| 14 | comments.append "\n" unless first == "\n" || body.empty? 15 | comments + body 16 | end 17 | end 18 | 19 | private 20 | 21 | attr_reader :comments, :body 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pragmater/processors/remover.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pragmater 4 | module Processors 5 | # Removes existing pragma comments. 6 | class Remover 7 | def initialize comments, body 8 | @comments = comments 9 | @body = body 10 | end 11 | 12 | def call 13 | body.first.then do |first_line| 14 | body.delete_at 0 if first_line == "\n" && comments.empty? 15 | comments + body 16 | end 17 | end 18 | 19 | private 20 | 21 | attr_reader :comments, :body 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pragmater/remover.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "refinements/pathname" 4 | 5 | module Pragmater 6 | # Removes pragma comments. 7 | class Remover 8 | include Dependencies[:settings] 9 | 10 | using Refinements::Pathname 11 | 12 | def initialize(parser: Parsers::File.new, **) 13 | @parser = parser 14 | super(**) 15 | end 16 | 17 | def call 18 | Pathname(settings.root_dir).files("{#{settings.patterns.join ","}}").map do |path| 19 | yield path if block_given? 20 | path.write parser.call(path, settings.comments, action: :remove).join 21 | end 22 | end 23 | 24 | private 25 | 26 | attr_reader :parser 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /pragmater.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "pragmater" 5 | spec.version = "16.2.1" 6 | spec.authors = ["Brooke Kuhlmann"] 7 | spec.email = ["brooke@alchemists.io"] 8 | spec.homepage = "https://alchemists.io/projects/pragmater" 9 | spec.summary = "A command line interface for managing pragma comments." 10 | spec.license = "Hippocratic-2.1" 11 | 12 | spec.metadata = { 13 | "bug_tracker_uri" => "https://github.com/bkuhlmann/pragmater/issues", 14 | "changelog_uri" => "https://alchemists.io/projects/pragmater/versions", 15 | "homepage_uri" => "https://alchemists.io/projects/pragmater", 16 | "funding_uri" => "https://github.com/sponsors/bkuhlmann", 17 | "label" => "Pragmater", 18 | "rubygems_mfa_required" => "true", 19 | "source_code_uri" => "https://github.com/bkuhlmann/pragmater" 20 | } 21 | 22 | spec.signing_key = Gem.default_key_path 23 | spec.cert_chain = [Gem.default_cert_path] 24 | 25 | spec.required_ruby_version = "~> 3.4" 26 | spec.add_dependency "cogger", "~> 1.0" 27 | spec.add_dependency "containable", "~> 1.1" 28 | spec.add_dependency "dry-schema", "~> 1.13" 29 | spec.add_dependency "etcher", "~> 3.0" 30 | spec.add_dependency "infusible", "~> 4.0" 31 | spec.add_dependency "refinements", "~> 13.0" 32 | spec.add_dependency "runcom", "~> 12.0" 33 | spec.add_dependency "sod", "~> 1.0" 34 | spec.add_dependency "spek", "~> 4.0" 35 | spec.add_dependency "zeitwerk", "~> 2.7" 36 | 37 | spec.bindir = "exe" 38 | spec.executables << "pragmater" 39 | spec.extra_rdoc_files = Dir["README*", "LICENSE*"] 40 | spec.files = Dir["*.gemspec", "lib/**/*"] 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib/pragmater/cli/actions/comment_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::CLI::Actions::Comment do 6 | subject(:action) { described_class.new } 7 | 8 | include_context "with application dependencies" 9 | 10 | describe "#call" do 11 | it "answers default comments" do 12 | action.call 13 | expect(settings.comments).to eq([]) 14 | end 15 | 16 | it "answers custom comment" do 17 | action.call ["# frozen_string_literal: true"] 18 | expect(settings.comments).to eq(["# frozen_string_literal: true"]) 19 | end 20 | 21 | it "answers custom comments" do 22 | action.call ["# auto_register: false", "# frozen_string_literal: true"] 23 | expect(settings.comments).to eq(["# auto_register: false", "# frozen_string_literal: true"]) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/lib/pragmater/cli/actions/pattern_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::CLI::Actions::Pattern do 6 | subject(:action) { described_class.new } 7 | 8 | include_context "with application dependencies" 9 | 10 | describe "#call" do 11 | it "answers default patterns" do 12 | action.call 13 | expect(settings.patterns).to eq([]) 14 | end 15 | 16 | it "answers custom pattern" do 17 | action.call [".md"] 18 | expect(settings.patterns).to eq([".md"]) 19 | end 20 | 21 | it "answers custom patterns" do 22 | action.call [".md", "**/*.md"] 23 | expect(settings.patterns).to eq([".md", "**/*.md"]) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/lib/pragmater/cli/actions/root_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::CLI::Actions::Root do 6 | subject(:action) { described_class.new } 7 | 8 | include_context "with application dependencies" 9 | 10 | describe "#call" do 11 | it "answers default root directory" do 12 | action.call 13 | expect(settings.root_dir).to eq(temp_dir) 14 | end 15 | 16 | it "answers custom root directory" do 17 | action.call "a/path" 18 | expect(settings.root_dir).to eq(Pathname("a/path")) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/lib/pragmater/cli/commands/insert_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::CLI::Commands::Insert do 6 | using Refinements::Struct 7 | using Refinements::Pathname 8 | using Refinements::StringIO 9 | 10 | subject(:command) { described_class.new } 11 | 12 | include_context "with application dependencies" 13 | 14 | describe "#call" do 15 | it "calls runner with default arguments" do 16 | expectation = proc { command.call } 17 | expect(&expectation).to output("").to_stdout 18 | end 19 | 20 | it "calls runner with custom arguments" do 21 | path = temp_dir.join("test.md").touch 22 | settings.patterns = %w[*.md] 23 | command.call 24 | 25 | expect(io.reread).to eq("#{path}\n") 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/pragmater/cli/commands/remove_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::CLI::Commands::Remove do 6 | using Refinements::Struct 7 | using Refinements::Pathname 8 | using Refinements::StringIO 9 | 10 | subject(:command) { described_class.new } 11 | 12 | include_context "with application dependencies" 13 | 14 | describe "#call" do 15 | it "calls runner with default arguments" do 16 | expectation = proc { command.call } 17 | expect(&expectation).to output("").to_stdout 18 | end 19 | 20 | it "calls runner with custom arguments" do 21 | path = temp_dir.join("test.md").touch 22 | settings.patterns = %w[*.md] 23 | command.call 24 | 25 | expect(io.reread).to eq("#{path}\n") 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/pragmater/cli/shell_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::CLI::Shell do 6 | using Refinements::Pathname 7 | using Refinements::Struct 8 | using Refinements::StringIO 9 | 10 | subject(:shell) { described_class.new } 11 | 12 | include_context "with application dependencies" 13 | 14 | before { Sod::Container.stub! logger:, io: } 15 | 16 | after { Sod::Container.restore } 17 | 18 | describe "#call" do 19 | let(:test_path) { temp_dir.join "test.rb" } 20 | 21 | let :options do 22 | [ 23 | "--root", 24 | temp_dir.to_s, 25 | "--comments", 26 | "# frozen_string_literal: true", 27 | "--patterns", 28 | "*.rb" 29 | ] 30 | end 31 | 32 | it "prints configuration usage" do 33 | shell.call %w[config] 34 | expect(io.reread).to match(/Manage configuration.+/m) 35 | end 36 | 37 | it "inserts pragma into file" do 38 | test_path.touch 39 | shell.call options.prepend("insert") 40 | 41 | expect(test_path.read).to eq("# frozen_string_literal: true\n") 42 | end 43 | 44 | it "removes pragma from files" do 45 | test_path.write "# frozen_string_literal: true\n" 46 | shell.call options.prepend("remove") 47 | 48 | expect(test_path.read).to eq("") 49 | end 50 | 51 | it "prints version" do 52 | shell.call %w[--version] 53 | expect(io.reread).to match(/Pragmater\s\d+\.\d+\.\d+/) 54 | end 55 | 56 | it "prints help" do 57 | shell.call %w[--help] 58 | expect(io.reread).to match(/Pragmater.+USAGE.+/m) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/lib/pragmater/configuration/model_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Configuration::Model do 6 | subject(:content) { described_class.new } 7 | 8 | describe "#initialize" do 9 | it "answers default attributes" do 10 | expect(content).to have_attributes(comments: [], patterns: [], root_dir: nil) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/lib/pragmater/formatters/general_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Formatters::General do 6 | subject(:formatter) { described_class.new string } 7 | 8 | describe "#call" do 9 | context "with formatted pragma" do 10 | let(:string) { "# frozen_string_literal: true" } 11 | 12 | it "answers original pragma" do 13 | expect(formatter.call).to eq("# frozen_string_literal: true") 14 | end 15 | end 16 | 17 | context "with unformatted pragma" do 18 | let(:string) { "#frozen_string_literal:true" } 19 | 20 | it "answers formatted pragma" do 21 | expect(formatter.call).to eq("# frozen_string_literal: true") 22 | end 23 | end 24 | 25 | context "with numbered encoding" do 26 | let(:string) { "# encoding: 1234" } 27 | 28 | it "answers original pragma" do 29 | expect(formatter.call).to eq("# encoding: 1234") 30 | end 31 | end 32 | 33 | context "with dashed encoding" do 34 | let(:string) { "# encoding: ISO-8859-1" } 35 | 36 | it "answers original pragma" do 37 | expect(formatter.call).to eq("# encoding: ISO-8859-1") 38 | end 39 | end 40 | 41 | context "with underscored encoding" do 42 | let(:string) { "# encoding: ASCII_8BIT" } 43 | 44 | it "answers original pragma" do 45 | expect(formatter.call).to eq("# encoding: ASCII_8BIT") 46 | end 47 | end 48 | 49 | context "with shebang" do 50 | let(:string) { "#! /usr/bin/ruby" } 51 | 52 | it "answers original pragma" do 53 | expect(formatter.call).to eq("#! /usr/bin/ruby") 54 | end 55 | end 56 | 57 | context "with comment" do 58 | let(:string) { "# Test." } 59 | 60 | it "answers original comment" do 61 | expect(formatter.call).to eq("# Test.") 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/lib/pragmater/formatters/main_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Formatters::Main do 6 | subject(:formatter) { described_class.new string } 7 | 8 | describe "#call" do 9 | context "when general" do 10 | let(:string) { "# frozen_string_literal: true" } 11 | 12 | it "answers general pragma" do 13 | expect(formatter.call).to eq("# frozen_string_literal: true") 14 | end 15 | end 16 | 17 | context "when shebang" do 18 | let(:string) { "#! /usr/bin/env ruby" } 19 | 20 | it "answers shebang pragma" do 21 | expect(formatter.call).to eq("#! /usr/bin/env ruby") 22 | end 23 | end 24 | 25 | context "when comment" do 26 | let(:string) { "# Some random comment." } 27 | 28 | it "answers comment" do 29 | expect(formatter.call).to eq("# Some random comment.") 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/lib/pragmater/formatters/shebang_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Formatters::Shebang do 6 | subject(:formatter) { described_class.new string } 7 | 8 | describe "#call" do 9 | context "with formatted shebang" do 10 | let(:string) { "#! /usr/bin/env ruby" } 11 | 12 | it "answers original pragma" do 13 | expect(formatter.call).to eq("#! /usr/bin/env ruby") 14 | end 15 | end 16 | 17 | context "with space missing between bang and forward slash" do 18 | let(:string) { "#!/usr/bin/env ruby" } 19 | 20 | it "answers formatted pragma" do 21 | expect(formatter.call).to eq("#! /usr/bin/env ruby") 22 | end 23 | end 24 | 25 | context "with octothorpe, bang, and path only" do 26 | let(:string) { "#!/ruby" } 27 | 28 | it "answers formatted pragma" do 29 | expect(formatter.call).to eq("#! /ruby") 30 | end 31 | end 32 | 33 | context "with space between octothorpe and bang" do 34 | let(:string) { "# ! /usr/bin/env ruby" } 35 | 36 | it "answers original comment" do 37 | expect(formatter.call).to eq("# ! /usr/bin/env ruby") 38 | end 39 | end 40 | 41 | context "with general comment" do 42 | let(:string) { "# Test." } 43 | 44 | it "answers original comment" do 45 | expect(formatter.call).to eq("# Test.") 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/lib/pragmater/inserter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Inserter do 6 | using Refinements::Pathname 7 | using Refinements::Struct 8 | using Refinements::StringIO 9 | 10 | subject(:inserter) { described_class.new } 11 | 12 | include_context "with application dependencies" 13 | 14 | describe "#call" do 15 | let :test_files do 16 | [ 17 | temp_dir.join("test.rb"), 18 | temp_dir.join("task", "test.rake"), 19 | temp_dir.join("test.txt") 20 | ] 21 | end 22 | 23 | before do 24 | settings.merge! comments: ["# encoding: UTF-8"], patterns: ["*.rb"] 25 | test_files.each { |path| path.make_ancestors.touch } 26 | end 27 | 28 | it "yields when given a block" do 29 | inserter.call { |path| io.print path } 30 | expect(io.reread).to eq(temp_dir.join("test.rb").to_s) 31 | end 32 | 33 | it "answers processed files" do 34 | expect(inserter.call).to contain_exactly(temp_dir.join("test.rb")) 35 | end 36 | 37 | it "modifies a single file with matching extension" do 38 | inserter.call 39 | expect(test_files.map(&:read)).to contain_exactly("", "", "# encoding: UTF-8\n") 40 | end 41 | 42 | it "modifies multiple files with matching extensions" do 43 | settings.patterns = ["*.rb", "*.txt"] 44 | inserter.call 45 | 46 | expect(test_files.map(&:read)).to contain_exactly( 47 | "", 48 | "# encoding: UTF-8\n", 49 | "# encoding: UTF-8\n" 50 | ) 51 | end 52 | 53 | it "modifies files with matching nested extensions" do 54 | settings.patterns = ["**/*.rake"] 55 | inserter.call 56 | 57 | expect(test_files.map(&:read)).to contain_exactly("# encoding: UTF-8\n", "", "") 58 | end 59 | 60 | it "doesn't modify files when patterns are included" do 61 | settings.patterns = [] 62 | inserter.call 63 | 64 | expect(test_files.map(&:read)).to contain_exactly("", "", "") 65 | end 66 | 67 | it "doesn't modify files when when extensions don't match" do 68 | settings.patterns = ["*.md"] 69 | inserter.call 70 | 71 | expect(test_files.map(&:read)).to contain_exactly("", "", "") 72 | end 73 | 74 | it "doesn't modify files with invalid extensions" do 75 | settings.patterns = ["bogus", "~#}*^"] 76 | inserter.call 77 | 78 | expect(test_files.map(&:read)).to contain_exactly("", "", "") 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/lib/pragmater/parsers/comments_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Parsers::Comments do 6 | subject(:commenter) { described_class.new older, newer } 7 | 8 | let(:older) { [] } 9 | let(:newer) { [] } 10 | 11 | describe "#insert" do 12 | context "with single newer comment" do 13 | let(:newer) { "# frozen_string_literal: true" } 14 | 15 | it "answers array of added comments" do 16 | expect(commenter.insert).to contain_exactly("# frozen_string_literal: true") 17 | end 18 | end 19 | 20 | context "with multiple newer comments" do 21 | let(:newer) { ["# encoding: UTF-8", "# frozen_string_literal: true"] } 22 | 23 | it "answers array of added comments" do 24 | expect(commenter.insert).to contain_exactly( 25 | "# encoding: UTF-8", 26 | "# frozen_string_literal: true" 27 | ) 28 | end 29 | end 30 | 31 | context "with single older comment" do 32 | let(:older) { "# encoding: UTF-8" } 33 | 34 | it "answers array of added comments" do 35 | expect(commenter.insert).to contain_exactly("# encoding: UTF-8") 36 | end 37 | end 38 | 39 | context "with multiple older comments" do 40 | let(:older) { ["# encoding: UTF-8", "# frozen_string_literal: true"] } 41 | 42 | it "answers array of added comments" do 43 | expect(commenter.insert).to contain_exactly( 44 | "# encoding: UTF-8", 45 | "# frozen_string_literal: true" 46 | ) 47 | end 48 | end 49 | 50 | context "with multiple older and newer comments" do 51 | let(:older) { ["# frozen_string_literal: true", "# example: test"] } 52 | let(:newer) { ["# encoding: UTF-8", "# coding: UTF-8"] } 53 | 54 | it "answers array of added comments" do 55 | expect(commenter.insert).to contain_exactly( 56 | "# frozen_string_literal: true", 57 | "# example: test", 58 | "# encoding: UTF-8", 59 | "# coding: UTF-8" 60 | ) 61 | end 62 | end 63 | 64 | context "with duplicate comments" do 65 | let(:older) { "# encoding: UTF-8" } 66 | let(:newer) { ["# frozen_string_literal: true", "# encoding: UTF-8"] } 67 | 68 | it "answers array of non-duplicated comments" do 69 | expect(commenter.insert).to contain_exactly( 70 | "# encoding: UTF-8", 71 | "# frozen_string_literal: true" 72 | ) 73 | end 74 | end 75 | 76 | context "with newer comment only" do 77 | let(:newer) { "# bogus" } 78 | 79 | it "answers array with original comment" do 80 | expect(commenter.insert).to contain_exactly("# bogus") 81 | end 82 | end 83 | 84 | context "with empty older and newer comments" do 85 | it "answers empty array" do 86 | expect(commenter.insert).to be_empty 87 | end 88 | end 89 | end 90 | 91 | describe "#remove" do 92 | context "with older and newer comments that match" do 93 | let(:older) { ["# frozen_string_literal: true", "# encoding: UTF-8", "# coding: UTF-8"] } 94 | let(:newer) { ["# coding: UTF-8", "# encoding: UTF-8"] } 95 | 96 | it "answers array with matches from older and newer comments removed" do 97 | expect(commenter.remove).to contain_exactly("# frozen_string_literal: true") 98 | end 99 | end 100 | 101 | context "with older and newer comments that don't match" do 102 | let(:older) { "# encoding: UTF-8" } 103 | let(:newer) { ["# frozen_string_literal: true", "# coding: UTF-8"] } 104 | 105 | it "answers array of older comments only" do 106 | expect(commenter.remove).to contain_exactly("# encoding: UTF-8") 107 | end 108 | end 109 | 110 | context "with empty older comments and array of newer comments" do 111 | let(:newer) { "# encoding: UTF-8" } 112 | 113 | it "answers empty array" do 114 | expect(commenter.remove).to be_empty 115 | end 116 | end 117 | 118 | context "with newer, invalid, comments" do 119 | let(:older) { "# bogus" } 120 | let(:newer) { "# bogus" } 121 | 122 | it "answers empty array" do 123 | expect(commenter.remove).to be_empty 124 | end 125 | end 126 | 127 | context "with empty older and newer comments" do 128 | it "answers empty array" do 129 | expect(commenter.remove).to be_empty 130 | end 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /spec/lib/pragmater/parsers/file_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Parsers::File do 6 | subject(:parser) { described_class.new } 7 | 8 | include_context "with temporary directory" 9 | 10 | let(:test_path) { temp_dir.join "test.rb" } 11 | 12 | before { FileUtils.cp fixture_path, test_path } 13 | 14 | describe "#call" do 15 | context "when adding to file with existing comments" do 16 | let(:fixture_path) { SPEC_ROOT.join "support/fixtures/with_comments.rb" } 17 | 18 | it "formats, adds comments, and spacing to top of file" do 19 | body = parser.call test_path, "# frozen_string_literal: true", action: :insert 20 | 21 | expect(body).to contain_exactly( 22 | "#! /usr/bin/env ruby\n", 23 | "# frozen_string_literal: true\n", 24 | "\n", 25 | "puts RUBY_VERSION\n" 26 | ) 27 | end 28 | end 29 | 30 | context "when adding to file with existing comments and spacing" do 31 | let(:fixture_path) { SPEC_ROOT.join "support/fixtures/with_comments_and_spacing.rb" } 32 | 33 | it "formats and adds comments with no extra spacing to top of file" do 34 | body = parser.call test_path, "# frozen_string_literal: true", action: :insert 35 | 36 | expect(body).to contain_exactly( 37 | "#! /usr/bin/env ruby\n", 38 | "# frozen_string_literal: true\n", 39 | "\n", 40 | "puts RUBY_VERSION\n" 41 | ) 42 | end 43 | end 44 | 45 | context "when adding to file with no comments" do 46 | let(:fixture_path) { Pathname File::NULL } 47 | 48 | it "adds formatted comments to top of file" do 49 | body = parser.call test_path, "# frozen_string_literal: true", action: :insert 50 | expect(body).to contain_exactly("# frozen_string_literal: true\n") 51 | end 52 | end 53 | 54 | context "when adding to file with duplicates" do 55 | let(:fixture_path) { SPEC_ROOT.join "support/fixtures/with_comments.rb" } 56 | 57 | it "does not add duplicates" do 58 | body = parser.call test_path, "#! /usr/bin/env ruby", action: :insert 59 | 60 | expect(body).to contain_exactly("#! /usr/bin/env ruby\n", "\n", "puts RUBY_VERSION\n") 61 | end 62 | end 63 | 64 | context "when removing from file with existing comments" do 65 | let(:fixture_path) { SPEC_ROOT.join "support/fixtures/with_comments.rb" } 66 | 67 | it "formats and removes comments" do 68 | body = parser.call test_path, "#! /usr/bin/env ruby", action: :remove 69 | expect(body).to contain_exactly("puts RUBY_VERSION\n") 70 | end 71 | end 72 | 73 | context "when removing from file with existing comments and spacing" do 74 | let(:fixture_path) { SPEC_ROOT.join "support/fixtures/with_comments_and_spacing.rb" } 75 | 76 | it "formats, removes comments, and removes trailing space" do 77 | body = parser.call test_path, "#! /usr/bin/env ruby", action: :remove 78 | expect(body).to contain_exactly("puts RUBY_VERSION\n") 79 | end 80 | end 81 | 82 | context "when remmoving from file with no comments" do 83 | let(:fixture_path) { Pathname File::NULL } 84 | 85 | it "does nothing" do 86 | body = parser.call test_path, "# frozen_string_literal: true", action: :remove 87 | expect(body).to be_empty 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/lib/pragmater/processors/handler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Processors::Handler do 6 | subject(:handler) { described_class.new } 7 | 8 | describe "#call" do 9 | it "handles insertion processing" do 10 | comments = ["# frozen_string_literal: true\n"] 11 | body = [%(puts "Test."\n)] 12 | 13 | expect(handler.call(:insert, comments, body)).to contain_exactly( 14 | "# frozen_string_literal: true\n", 15 | "\n", 16 | %(puts "Test."\n) 17 | ) 18 | end 19 | 20 | it "handles removal processing" do 21 | comments = [] 22 | body = ["\n", %(puts "Test."\n)] 23 | 24 | expect(handler.call(:remove, comments, body)).to contain_exactly(%(puts "Test."\n)) 25 | end 26 | 27 | it "fails with key error for unknown action" do 28 | expectation = proc { handler.call :bogus, [], [] } 29 | expect(&expectation).to raise_error(KeyError, /bogus/) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/lib/pragmater/processors/inserter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Processors::Inserter do 6 | subject(:processor) { described_class.new ["# frozen_string_literal: true\n"], body } 7 | 8 | describe "#call" do 9 | context "when body doesn't start with new line" do 10 | let(:body) { [%(puts "Test."\n)] } 11 | 12 | it "adds new line between pragmas and body" do 13 | expect(processor.call).to contain_exactly( 14 | "# frozen_string_literal: true\n", 15 | "\n", 16 | %(puts "Test."\n) 17 | ) 18 | end 19 | end 20 | 21 | context "when body starts with new line" do 22 | let(:body) { ["\n", %(puts "Test."\n)] } 23 | 24 | it "doesn't add new line" do 25 | expect(processor.call).to contain_exactly( 26 | "# frozen_string_literal: true\n", 27 | "\n", 28 | %(puts "Test."\n) 29 | ) 30 | end 31 | end 32 | 33 | context "when body is empty" do 34 | let(:body) { [] } 35 | 36 | it "doesn't add new line" do 37 | expect(processor.call).to contain_exactly("# frozen_string_literal: true\n") 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib/pragmater/processors/remover_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Processors::Remover do 6 | subject(:processor) { described_class.new comments, body } 7 | 8 | describe "#call" do 9 | context "when comments are empty and body starts with new line" do 10 | let(:comments) { [] } 11 | let(:body) { ["\n", %(puts "Test."\n)] } 12 | 13 | it "deletes new line" do 14 | expect(processor.call).to contain_exactly(%(puts "Test."\n)) 15 | end 16 | end 17 | 18 | context "when comments are empty and body doesn't start with new line" do 19 | let(:comments) { [] } 20 | let(:body) { [%(puts "Test."\n)] } 21 | 22 | it "doesn't delete any line" do 23 | expect(processor.call).to contain_exactly(%(puts "Test."\n)) 24 | end 25 | end 26 | 27 | context "when comments exist and body starts with new line" do 28 | let(:comments) { ["#! /usr/bin/env ruby\n"] } 29 | let(:body) { ["\n", %(puts "Test."\n)] } 30 | 31 | it "doesn't delete any line" do 32 | expect(processor.call).to contain_exactly( 33 | "#! /usr/bin/env ruby\n", 34 | "\n", 35 | %(puts "Test."\n) 36 | ) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/lib/pragmater/remover_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater::Remover do 6 | using Refinements::Pathname 7 | using Refinements::Struct 8 | using Refinements::StringIO 9 | 10 | subject(:remover) { described_class.new } 11 | 12 | include_context "with application dependencies" 13 | 14 | describe "#call" do 15 | let :test_files do 16 | [ 17 | temp_dir.join("test.rb"), 18 | temp_dir.join("task", "test.rake"), 19 | temp_dir.join("test.txt") 20 | ] 21 | end 22 | 23 | before do 24 | settings.merge! patterns: ["*.rb"], comments: ["# encoding: UTF-8"] 25 | test_files.each { |path| path.make_ancestors.touch } 26 | end 27 | 28 | it "yields when given a block" do 29 | remover.call { |path| io.print path } 30 | expect(io.reread).to eq(temp_dir.join("test.rb").to_s) 31 | end 32 | 33 | it "answers processed files" do 34 | expect(remover.call).to contain_exactly(temp_dir.join("test.rb")) 35 | end 36 | 37 | it "modify files with matching extensions" do 38 | temp_dir.join("test.rb").rewrite { |body| "# encoding: UTF-8\n#{body}" } 39 | remover.call 40 | 41 | expect(test_files.map(&:read)).to contain_exactly("", "", "") 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/lib/pragmater_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Pragmater do 6 | describe ".loader" do 7 | it "eager loads" do 8 | expectation = proc { described_class.loader.eager_load force: true } 9 | expect(&expectation).not_to raise_error 10 | end 11 | 12 | it "answers unique tag" do 13 | expect(described_class.loader.tag).to eq("pragmater") 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "simplecov" 4 | 5 | unless ENV["NO_COVERAGE"] 6 | SimpleCov.start do 7 | add_filter %r((.+/container\.rb|^/spec/)) 8 | enable_coverage :branch 9 | enable_coverage_for_eval 10 | minimum_coverage_by_file line: 95, branch: 95 11 | end 12 | end 13 | 14 | Bundler.require :tools 15 | 16 | require "pragmater" 17 | require "refinements" 18 | 19 | SPEC_ROOT = Pathname(__dir__).realpath.freeze 20 | 21 | using Refinements::Pathname 22 | 23 | Pathname.require_tree SPEC_ROOT.join("support/shared_contexts") 24 | Pathname.require_tree SPEC_ROOT.join("support/shared_examples") 25 | 26 | RSpec.configure do |config| 27 | config.color = true 28 | config.disable_monkey_patching! 29 | config.example_status_persistence_file_path = "./tmp/rspec-examples.txt" 30 | config.filter_run_when_matching :focus 31 | config.formatter = ENV.fetch("CI", false) == "true" ? :progress : :documentation 32 | config.order = :random 33 | config.pending_failure_output = :no_backtrace 34 | config.shared_context_metadata_behavior = :apply_to_host_groups 35 | config.warnings = true 36 | 37 | config.expect_with :rspec do |expectations| 38 | expectations.syntax = :expect 39 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 40 | end 41 | 42 | config.mock_with :rspec do |mocks| 43 | mocks.verify_doubled_constant_names = true 44 | mocks.verify_partial_doubles = true 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/support/fixtures/with_comments.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | puts RUBY_VERSION 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/with_comments_and_spacing.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | puts RUBY_VERSION 4 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/application_dependencies.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.shared_context "with application dependencies" do 4 | using Refinements::Struct 5 | 6 | include_context "with temporary directory" 7 | 8 | let(:settings) { Pragmater::Container[:settings] } 9 | let(:logger) { Cogger.new id: :pragmater, io: StringIO.new } 10 | let(:io) { StringIO.new } 11 | 12 | before do 13 | settings.merge! Etcher.call( 14 | Pragmater::Container[:registry].remove_loader(1), 15 | root_dir: temp_dir 16 | ) 17 | 18 | Pragmater::Container.stub! logger:, io: 19 | end 20 | 21 | after { Pragmater::Container.restore } 22 | end 23 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/temp_dir.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.shared_context "with temporary directory" do 4 | let(:temp_dir) { Bundler.root.join "tmp/rspec" } 5 | 6 | around do |example| 7 | temp_dir.mkpath 8 | example.run 9 | temp_dir.rmtree 10 | end 11 | end 12 | --------------------------------------------------------------------------------