semverve 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f0366a11f400a32928725b5f954c9f71e3659a75cb5757cf268a3efc018659c
4
- data.tar.gz: 870ae675510264ea80bc7a44dbe86efd5248a4020a98f3884b3123cb1f68bea6
3
+ metadata.gz: b04f97b571e0eed652d155221cddb5afe06d703f2a27546f15c11e3d26a3d214
4
+ data.tar.gz: 6c824db22d1967e9daa8de369ca199b6cc7969f6ab2179933600fbba34d1aa6e
5
5
  SHA512:
6
- metadata.gz: e945ab75c4b9b4eaf3d232ccc5f0be75b7c024a37e9f5488c2a9d31cefe1bfbd1bc6105b27f1b9d51d600d393ea2ca5b3a51445d9bf712ef279a362192ba5166
7
- data.tar.gz: 5dcdc6bb2287825fe348c84c289878f76b06e2b453257848990dc1c9cf42a3581dbafc2fd65e04d88ed2861a2f2c17496ce865d929823c5f8bd36e4e8acff13e
6
+ metadata.gz: 31208572bd96d1d1bb00537673e9960594501a2b4226f821114d8cb624965fd0cd7282722d6284cf0666f80a908706174a3ca3d0bb4109a3b18dc882e9a26ecd
7
+ data.tar.gz: 8deb952db13cfdf0f8a13fbbcffb07afc23c402ece2efc3d50899d4158ea7455a6bedc993cb79c9d3de8dfc83c8142981f87f52777ee79a8dba1c1b09bc58dcd
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  semverve-*.gem
2
2
  coverage/*
3
3
  docs/*
4
+ _config.yml
data/CONTRIBUTING.md CHANGED
@@ -100,6 +100,27 @@ When changing behavior, update the README at the same time. The README is the
100
100
  primary user guide, so examples should match real task names, configuration
101
101
  names, defaults, and output.
102
102
 
103
+ ## Rake Task Interface Design
104
+ Semverve should feel natural from a shell while staying Rake-native. Prefer
105
+ Rake task arguments for direct task inputs:
106
+
107
+ ```sh
108
+ bundle exec rake 'semverve:set[x.y.z]'
109
+ bundle exec rake 'semverve:generate[simple,force]'
110
+ ```
111
+
112
+ For tasks with more than one optional value, prefer meaning-bearing tokens over
113
+ strict positional placeholders. For example, `semverve:generate` treats semantic
114
+ versions as the generated version, `module` or `simple` as the format, and
115
+ `force` as the overwrite mode. That keeps invocations like
116
+ `rake 'semverve:generate[simple]'` and `rake 'semverve:generate[force]'`
117
+ readable without requiring awkward empty slots.
118
+
119
+ Reserve environment variables for cross-cutting runtime toggles that compose
120
+ across related tasks, such as `SEMVERVE_REPORT_IGNORED=true rake
121
+ semverve:check`. Avoid generic environment variables like `VERSION`, `FORMAT`,
122
+ or `FORCE`; they can collide with a user's shell, CI, or parent build process.
123
+
103
124
  ## Versioning and Release Checks
104
125
  Semverve uses Semverve to maintain itself, which doubles as useful
105
126
  smoke-testing. The project Rakefile requires `lib/semverve/task` and installs
@@ -123,7 +144,7 @@ bundle exec rake semverve:current
123
144
  bundle exec rake semverve:increment:patch
124
145
  bundle exec rake semverve:increment:minor
125
146
  bundle exec rake semverve:increment:major
126
- bundle exec rake semverve:set VERSION=x.y.z
147
+ bundle exec rake 'semverve:set[x.y.z]'
127
148
  bundle exec rake semverve:check
128
149
  bundle exec rake semverve:fix
129
150
  ```
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- semverve (0.1.0)
4
+ semverve (0.2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -84,10 +84,10 @@ PLATFORMS
84
84
  DEPENDENCIES
85
85
  rake (~> 13.0, >= 13.0.1)
86
86
  rdoc
87
+ semverve!
87
88
  simplecov
88
89
  standard (~> 1.54.0)
89
90
  test-unit (~> 3.3, >= 3.3.5)
90
- semverve!
91
91
 
92
92
  BUNDLED WITH
93
93
  4.0.10
data/README.md CHANGED
@@ -1,4 +1,9 @@
1
1
  # Semverve
2
+ [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fevanthegrayt%2Fsemverve%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/evanthegrayt/semverve/goto?ref=master)
3
+ [![Language: Ruby](https://img.shields.io/static/v1?label=language&message=Ruby&color=CC342D&style=flat&logo=ruby&logoColor=CC342D)](https://www.ruby-lang.org/)
4
+ [![Gem Version](https://img.shields.io/gem/v/semverve.svg)](https://rubygems.org/gems/semverve)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
2
7
  Rake tasks for handling the tedium surrounding maintaining a version number in
3
8
  your Ruby project, with gusto!
4
9
 
@@ -26,6 +31,9 @@ tasks can update safe references and run `bundle lock` for generated lockfile
26
31
  drift. Specific findings can be skipped with magic comments, similar to RuboCop
27
32
  and RDoc.
28
33
 
34
+ You can view the documentation
35
+ [here](https://evanthegrayt.github.io/semverve/).
36
+
29
37
  ## Installation
30
38
  Add the gem to your Gemfile:
31
39
 
@@ -47,7 +55,7 @@ rake semverve:increment:patch
47
55
  rake semverve:increment:minor
48
56
  rake semverve:increment:major
49
57
  rake semverve:generate
50
- rake semverve:set VERSION=1.2.3
58
+ rake 'semverve:set[1.2.3]'
51
59
  rake semverve:check
52
60
  rake semverve:fix
53
61
  rake semverve:check:references
@@ -56,6 +64,8 @@ rake semverve:check:code
56
64
  rake semverve:fix:code
57
65
  rake semverve:check:metadata
58
66
  rake semverve:fix:metadata
67
+ rake semverve:check:rubygems
68
+ rake semverve:check:release
59
69
  ```
60
70
 
61
71
  ## Configuration
@@ -81,6 +91,8 @@ Semverve.configure do |config|
81
91
  config.version_file = "lib/my_gem/version.rb"
82
92
  config.module_name = "MyGem"
83
93
  config.version_checks = [:doc_references, :code_references, :metadata]
94
+ config.release_checks = [:rubygems]
95
+ config.rubygems_host = "https://rubygems.org"
84
96
  config.version_code_reference_files.append("lib/**/*.rb")
85
97
  config.version_doc_reference_files.append("doc/**/*.md")
86
98
  config.version_reference_mode = :non_current
@@ -95,6 +107,8 @@ Semverve.configure do |config|
95
107
  config.bundle_lock = false
96
108
  config.root = Dir.pwd
97
109
  config.version_checks = [:doc_references, :code_references, :metadata]
110
+ config.release_checks = []
111
+ config.rubygems_host = "https://rubygems.org"
98
112
  config.version_reference_mode = :older
99
113
  config.version_code_reference_files = Rake::FileList[]
100
114
  config.version_code_reference_pattern =
@@ -111,6 +125,23 @@ end
111
125
  The empty `version_code_reference_files` default only applies to arbitrary code
112
126
  literal scanning. `rake semverve:check` still checks the resolved `.gemspec`
113
127
  version and matching `Gemfile.lock` entry through its default metadata check.
128
+ Release checks are empty by default because they may make network requests and
129
+ are intended for release pipelines rather than every local or pull-request run.
130
+
131
+ Semverve tasks use Rake task arguments for values:
132
+
133
+ ```sh
134
+ rake 'semverve:set[1.2.3]'
135
+ rake 'semverve:generate[1.0.0,simple]'
136
+ rake 'semverve:generate[simple]'
137
+ rake 'semverve:generate[force]'
138
+ ```
139
+
140
+ Quote task invocations that include square brackets. Shells such as zsh may
141
+ otherwise treat brackets as glob patterns before Rake sees them. Flag syntax
142
+ such as `rake semverve:set --version 1.2.3` is not used because `--version` is
143
+ already a Rake option; Semverve stays within Rake's native argument syntax
144
+ instead.
114
145
 
115
146
  The gem name, module name, and version-file path are inferred by default:
116
147
 
@@ -138,48 +169,6 @@ Semverve::Task.new do |config|
138
169
  end
139
170
  ```
140
171
 
141
- ## Publishing generated docs
142
- Semverve also includes an optional GitHub Pages publishing task for generated
143
- documentation. It builds docs on your current branch, opens the publishing
144
- branch in a temporary Git worktree, copies the generated docs there, commits any
145
- changes, and pushes the publishing branch.
146
-
147
- Add this to your Rakefile:
148
-
149
- ```ruby
150
- require "semverve/docs_publisher/task"
151
-
152
- Semverve::DocsPublisher::Task.new
153
- ```
154
-
155
- This defines:
156
-
157
- ```text
158
- rake docs:publish
159
- rake docs:publish:dry_run
160
- ```
161
-
162
- The defaults are equivalent to:
163
-
164
- ```ruby
165
- Semverve::DocsPublisher::Task.new do |task|
166
- task.root = Dir.pwd
167
- task.build_task = "rerdoc"
168
- task.source_dir = "docs"
169
- task.target_dir = "docs"
170
- task.branch = "gh-pages"
171
- task.remote = "origin"
172
- task.commit_message = "Update generated documentation"
173
- task.allow_dirty = false
174
- task.push = true
175
- task.output = $stdout
176
- end
177
- ```
178
-
179
- `docs:publish` requires a clean working tree by default. Set
180
- `task.allow_dirty = true` only when you intentionally want to publish generated
181
- documentation from uncommitted source changes.
182
-
183
172
  ## Rails apps
184
173
  Rails applications do not need gem-style version files, but an application
185
174
  version can still be useful for release notes, support/debug screens,
@@ -261,13 +250,25 @@ rake semverve:generate
261
250
  Generate a specific version or format:
262
251
 
263
252
  ```sh
264
- rake semverve:generate VERSION=1.0.0 FORMAT=simple
253
+ rake 'semverve:generate[1.0.0,simple]'
254
+ rake 'semverve:generate[simple]'
265
255
  ```
266
256
 
267
257
  Generation fails if the target file already exists. To replace it:
268
258
 
269
259
  ```sh
270
- rake semverve:generate FORCE=true
260
+ rake 'semverve:generate[force]'
261
+ ```
262
+
263
+ `semverve:generate` accepts optional tokens for version, format, and force.
264
+ Token order does not matter: semantic versions set the generated version,
265
+ `module` or `simple` sets the format, and `force` overwrites an existing version
266
+ file.
267
+
268
+ ```sh
269
+ rake 'semverve:generate[1.0.0,force]'
270
+ rake 'semverve:generate[simple,force]'
271
+ rake 'semverve:generate[1.0.0,simple,force]'
271
272
  ```
272
273
 
273
274
  ## Incrementing
@@ -293,7 +294,7 @@ your gem's version in `Gemfile.lock`.
293
294
  Set an exact version without incrementing:
294
295
 
295
296
  ```sh
296
- rake semverve:set VERSION=1.2.3
297
+ rake 'semverve:set[1.2.3]'
297
298
  ```
298
299
 
299
300
  Successful updates print the version change:
@@ -325,6 +326,9 @@ Run every version check with:
325
326
  rake semverve:check
326
327
  ```
327
328
 
329
+ This task is designed for normal CI. It uses local project files, prints
330
+ parseable findings, and exits non-zero when it finds drift.
331
+
328
332
  By default, this checks:
329
333
 
330
334
  - README version references, plus any configured docs or comment files
@@ -430,6 +434,13 @@ same line or the preceding nonblank line.
430
434
  This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->
431
435
  ```
432
436
 
437
+ Audit ignored references by setting `SEMVERVE_REPORT_IGNORED=true` when running
438
+ check tasks:
439
+
440
+ ```sh
441
+ SEMVERVE_REPORT_IGNORED=true rake semverve:check
442
+ ```
443
+
433
444
  ### Code version literals
434
445
  Code scanning is opt-in to avoid false positives. This is for arbitrary project
435
446
  code, not gem metadata. The default is:
@@ -463,6 +474,13 @@ APP_VERSION = "1.2.2"
463
474
  spec.version = "1.2.2"
464
475
  ```
465
476
 
477
+ Ignore an intentional code literal with `semverve:ignore-version-reference` on
478
+ the same line or the preceding nonblank line.
479
+
480
+ Set `SEMVERVE_REPORT_IGNORED=true` with `semverve:check` or
481
+ `semverve:check:code` to report ignored stale literals without changing
482
+ `semverve:fix` behavior.
483
+
466
484
  The default pattern is Ruby-oriented. Semverve does not inspect file extensions
467
485
  or parse other languages for code literals; non-Ruby files are scanned as plain
468
486
  text with the same pattern. If a JavaScript, Python, or other source file uses a
@@ -536,6 +554,110 @@ end
536
554
  `rake semverve:fix:metadata` updates safe literal gemspec assignments and
537
555
  runs `bundle lock` when the lockfile has drifted.
538
556
 
557
+ ## Checking release readiness
558
+ Release checks are separate from `rake semverve:check`. They are useful in CI,
559
+ but they are meant for release workflows, tag builds, or pre-publish jobs rather
560
+ than every pull request.
561
+
562
+ Enable the RubyGems published-version check:
563
+
564
+ ```ruby
565
+ Semverve.configure do |config|
566
+ config.release_checks = [:rubygems]
567
+ end
568
+ ```
569
+
570
+ Then run:
571
+
572
+ ```sh
573
+ rake semverve:check:release
574
+ ```
575
+
576
+ With `:rubygems` enabled, `semverve:check:release` asks the configured
577
+ RubyGems-compatible host whether the current version already exists. If it does,
578
+ the task exits non-zero with a message like:
579
+
580
+ ```text
581
+ my_gem 1.2.3 already exists on https://rubygems.org.
582
+ ```
583
+
584
+ If all configured release checks pass, it prints:
585
+
586
+ ```text
587
+ Release checks passed.
588
+ ```
589
+
590
+ You can also run the RubyGems check directly without changing
591
+ `config.release_checks`:
592
+
593
+ ```sh
594
+ rake semverve:check:rubygems
595
+ ```
596
+
597
+ When the current version is not published, the focused task prints:
598
+
599
+ ```text
600
+ my_gem 1.2.3 is not published on https://rubygems.org.
601
+ ```
602
+
603
+ Use `config.rubygems_host` for a private RubyGems-compatible server:
604
+
605
+ ```ruby
606
+ Semverve.configure do |config|
607
+ config.release_checks = [:rubygems]
608
+ config.rubygems_host = "https://gems.example.com"
609
+ end
610
+ ```
611
+
612
+ The published-version check treats a missing gem as unpublished. It fails closed
613
+ on registry errors, malformed responses, and network failures because release
614
+ pipelines should not silently publish after an inconclusive preflight.
615
+
616
+ For ordinary CI, keep using the local checks:
617
+
618
+ ```sh
619
+ bundle exec rake test semverve:check
620
+ ```
621
+
622
+ For release CI, run the release check before building or pushing:
623
+
624
+ ```sh
625
+ bundle exec rake semverve:check:release build
626
+ ```
627
+
628
+ ## Vim
629
+ While there's no official vim support (yet), you can add the following to
630
+ `~/.vim/plugin/semverve.vim`.
631
+
632
+ ```vim
633
+ command! -bang SemverveAudit call <SID>semverve_audit(<bang>0)
634
+ function! s:semverve_audit(report_ignored) abort
635
+ let l:old_efm = &errorformat
636
+ try
637
+ let &errorformat = '%f:%l:%c:%m'
638
+ let l:string = ""
639
+ if !a:report_ignored
640
+ let l:string .= 'SEMVERVE_REPORT_IGNORED=true '
641
+ endif
642
+ let l:string .= 'bundle exec rake semverve:check 2>/dev/null'
643
+ cexpr systemlist(l:string)
644
+ if v:shell_error != 0
645
+ copen
646
+ else
647
+ cclose
648
+ echo 'Semverve checks passed.'
649
+ endif
650
+ finally
651
+ let &errorformat = l:old_efm
652
+ endtry
653
+ endfunction
654
+ ```
655
+
656
+ You can then call `:SemverveAudit`, which will call `bundle exec rake
657
+ semverve:check`, and `:SemverveAudit!` which will call the same command with
658
+ `SEMVERVE_REPORT_IGNORED=true`, and populate and open the quickfix list if any
659
+ offenses are found.
660
+
539
661
  ## Reporting Bugs and Requesting Features
540
662
  If you have an idea or find a bug, please [create an
541
663
  issue](https://github.com/evanthegrayt/semverve/issues/new). Just make sure
@@ -16,6 +16,12 @@ module Semverve
16
16
  # @return [Array<Symbol>]
17
17
  VALID_VERSION_CHECKS = [:doc_references, :code_references, :metadata].freeze
18
18
 
19
+ ##
20
+ # Release-readiness surfaces supported by release check tasks.
21
+ #
22
+ # @return [Array<Symbol>]
23
+ VALID_RELEASE_CHECKS = [:rubygems].freeze
24
+
19
25
  ##
20
26
  # Default Ruby pattern for code version literals that are safe to rewrite.
21
27
  #
@@ -58,12 +64,24 @@ module Semverve
58
64
  # @return [Symbol, String, nil]
59
65
  attr_reader :preset
60
66
 
67
+ ##
68
+ # Release-readiness surfaces run by the release check task.
69
+ #
70
+ # @return [Array<Symbol, String>]
71
+ attr_reader :release_checks
72
+
61
73
  ##
62
74
  # Project root used when inferring metadata and expanding paths.
63
75
  #
64
76
  # @return [String, nil]
65
77
  attr_reader :root
66
78
 
79
+ ##
80
+ # RubyGems-compatible host used for published-version checks.
81
+ #
82
+ # @return [String]
83
+ attr_accessor :rubygems_host
84
+
67
85
  ##
68
86
  # Explicit version-file path relative to the project root.
69
87
  #
@@ -110,6 +128,7 @@ module Semverve
110
128
  @command_runner = ->(command) { system(command) }
111
129
  @format = :module
112
130
  @preset = nil
131
+ @rubygems_host = "https://rubygems.org"
113
132
  @version_code_reference_files = Rake::FileList[]
114
133
  self.version_code_reference_pattern = DEFAULT_VERSION_CODE_REFERENCE_PATTERN
115
134
  @version_doc_reference_files = Rake::FileList["README*", "**/README*"].exclude(
@@ -119,6 +138,7 @@ module Semverve
119
138
  "vendor/**/*"
120
139
  )
121
140
  @version_reference_mode = :older
141
+ self.release_checks = []
122
142
  self.version_checks = VALID_VERSION_CHECKS
123
143
  end
124
144
 
@@ -135,7 +155,9 @@ module Semverve
135
155
  format: normalized_format,
136
156
  gem_name: metadata.gem_name,
137
157
  module_name: metadata.module_name,
158
+ release_checks: normalized_release_checks,
138
159
  root: expanded_root,
160
+ rubygems_host: rubygems_host,
139
161
  version_file: metadata.version_file,
140
162
  version_checks: normalized_version_checks,
141
163
  version_code_reference_files: version_code_reference_files,
@@ -186,6 +208,16 @@ module Semverve
186
208
  @preset = preset.nil? ? nil : Presets.fetch(preset).name
187
209
  end
188
210
 
211
+ ##
212
+ # Sets the release-readiness surfaces run by the release check task.
213
+ #
214
+ # @param [Array<Symbol, String>] checks
215
+ #
216
+ # @return [Array<Symbol>]
217
+ def release_checks=(checks)
218
+ @release_checks = normalize_release_checks(checks)
219
+ end
220
+
189
221
  ##
190
222
  # Sets the project root used when expanding paths.
191
223
  #
@@ -243,6 +275,14 @@ module Semverve
243
275
  resolved_value(:format).to_sym
244
276
  end
245
277
 
278
+ ##
279
+ # Configured release checks normalized for lookup.
280
+ #
281
+ # @return [Array<Symbol>]
282
+ def normalized_release_checks
283
+ normalize_release_checks(release_checks)
284
+ end
285
+
246
286
  ##
247
287
  # Resolved value for a configurable attribute before metadata inference.
248
288
  #
@@ -290,6 +330,22 @@ module Semverve
290
330
  raise Error, "Unknown version check #{invalid_checks.map(&:inspect).join(", ")}. Use #{valid_checks}."
291
331
  end
292
332
 
333
+ ##
334
+ # Normalizes and validates release checks.
335
+ #
336
+ # @param [Array<Symbol, String>] checks
337
+ #
338
+ # @return [Array<Symbol>]
339
+ def normalize_release_checks(checks)
340
+ normalized_checks = Array(checks).map do |check|
341
+ check.respond_to?(:to_sym) ? check.to_sym : check
342
+ end
343
+ return normalized_checks if normalized_checks.all? { |check| VALID_RELEASE_CHECKS.include?(check) }
344
+
345
+ invalid_checks = normalized_checks.reject { |check| VALID_RELEASE_CHECKS.include?(check) }
346
+ raise Error, "Unknown release check #{invalid_checks.map(&:inspect).join(", ")}. Use :rubygems."
347
+ end
348
+
293
349
  ##
294
350
  # Validates the configured code reference pattern.
295
351
  #
@@ -373,12 +429,24 @@ module Semverve
373
429
  # @return [String]
374
430
  attr_reader :module_name
375
431
 
432
+ ##
433
+ # Resolved release-readiness surfaces run by the release check task.
434
+ #
435
+ # @return [Array<Symbol>]
436
+ attr_reader :release_checks
437
+
376
438
  ##
377
439
  # Absolute project root.
378
440
  #
379
441
  # @return [String]
380
442
  attr_reader :root
381
443
 
444
+ ##
445
+ # Resolved RubyGems-compatible host used for published-version checks.
446
+ #
447
+ # @return [String]
448
+ attr_reader :rubygems_host
449
+
382
450
  ##
383
451
  # Resolved version-file path relative to the project root.
384
452
  #
@@ -423,7 +491,9 @@ module Semverve
423
491
  # @param [Symbol] format
424
492
  # @param [String] gem_name
425
493
  # @param [String] module_name
494
+ # @param [Array<Symbol>] release_checks
426
495
  # @param [String] root
496
+ # @param [String] rubygems_host
427
497
  # @param [String] version_file
428
498
  # @param [Array<Symbol>] version_checks
429
499
  # @param [Rake::FileList] version_code_reference_files
@@ -438,7 +508,9 @@ module Semverve
438
508
  format:,
439
509
  gem_name:,
440
510
  module_name:,
511
+ release_checks:,
441
512
  root:,
513
+ rubygems_host:,
442
514
  version_file:,
443
515
  version_checks:,
444
516
  version_code_reference_files:,
@@ -451,7 +523,9 @@ module Semverve
451
523
  @format = format
452
524
  @gem_name = gem_name
453
525
  @module_name = module_name
526
+ @release_checks = release_checks
454
527
  @root = root
528
+ @rubygems_host = rubygems_host
455
529
  @version_file = version_file
456
530
  @version_checks = version_checks
457
531
  @version_code_reference_files = version_code_reference_files
@@ -11,21 +11,25 @@ module Semverve
11
11
  # Generates new Ruby version files from Semverve configuration.
12
12
  class Generator
13
13
  ##
14
- # Default version used when +VERSION+ is not provided.
14
+ # Default version used when a version argument is not provided.
15
15
  #
16
16
  # @return [String]
17
- DEFAULT_VERSION = "0.1.0"
17
+ DEFAULT_VERSION = "0.1.0" # semverve:ignore-version-reference
18
18
 
19
19
  ##
20
20
  # Initializes a version-file generator.
21
21
  #
22
22
  # @param [Semverve::ResolvedConfiguration] configuration
23
- # @param [#fetch] env
23
+ # @param [String, nil] version
24
+ # @param [String, Symbol, nil] format
25
+ # @param [Boolean] force
24
26
  #
25
27
  # @return [Semverve::Generator]
26
- def initialize(configuration, env = ENV)
28
+ def initialize(configuration, version: nil, format: nil, force: false)
27
29
  @configuration = configuration
28
- @env = env
30
+ @version = version
31
+ @format = format
32
+ @force = force
29
33
  end
30
34
 
31
35
  ##
@@ -33,12 +37,12 @@ module Semverve
33
37
  #
34
38
  # @return [String]
35
39
  def generate
36
- version = SemanticVersion.parse(env.fetch("VERSION", DEFAULT_VERSION))
37
- format = Formats.fetch(env.fetch("FORMAT", configuration.format))
40
+ version = SemanticVersion.parse(requested_version)
41
+ format = Formats.fetch(requested_format)
38
42
  path = configuration.absolute_version_file
39
43
 
40
44
  if File.exist?(path) && !force?
41
- raise Error, "Version file already exists at #{path}. Set FORCE=true to overwrite it."
45
+ raise Error, "Version file already exists at #{path}. Run rake 'semverve:generate[force]' to overwrite it."
42
46
  end
43
47
 
44
48
  FileUtils.mkdir_p(File.dirname(path))
@@ -53,14 +57,30 @@ module Semverve
53
57
  # Resolved configuration used for generation.
54
58
  #
55
59
  # @return [Semverve::ResolvedConfiguration]
56
- attr_reader :configuration, :env
60
+ attr_reader :configuration, :version, :format, :force
61
+
62
+ ##
63
+ # Requested version or default.
64
+ #
65
+ # @return [String]
66
+ def requested_version
67
+ version || DEFAULT_VERSION
68
+ end
69
+
70
+ ##
71
+ # Requested format or configured default.
72
+ #
73
+ # @return [String, Symbol]
74
+ def requested_format
75
+ format || configuration.format
76
+ end
57
77
 
58
78
  ##
59
79
  # Whether generation should overwrite an existing version file.
60
80
  #
61
81
  # @return [Boolean]
62
82
  def force?
63
- env.fetch("FORCE", "false").match?(/\A(true|1|yes)\z/i)
83
+ force
64
84
  end
65
85
  end
66
86
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "timeout"
6
+ require "uri"
7
+
8
+ require_relative "error"
9
+
10
+ module Semverve
11
+ ##
12
+ # Checks whether the current gem version already exists on a
13
+ # RubyGems-compatible host.
14
+ class PublishedVersion
15
+ class << self
16
+ ##
17
+ # Callable used to fetch RubyGems-compatible API responses.
18
+ #
19
+ # @return [#call]
20
+ attr_writer :http_getter
21
+
22
+ ##
23
+ # Returns the HTTP response fetcher.
24
+ #
25
+ # @return [#call]
26
+ def http_getter
27
+ @http_getter ||= ->(uri) { Net::HTTP.get_response(uri) }
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Initializes a published-version check.
33
+ #
34
+ # @param [Semverve::ResolvedConfiguration] configuration
35
+ # @param [Semverve::SemanticVersion] current_version
36
+ #
37
+ # @return [Semverve::PublishedVersion]
38
+ def initialize(configuration, current_version)
39
+ @configuration = configuration
40
+ @current_version = current_version
41
+ end
42
+
43
+ ##
44
+ # Whether the current version already exists on the configured host.
45
+ #
46
+ # @return [Boolean]
47
+ def published?
48
+ versions.any? { |version| version.fetch("number", nil).to_s == current_version.to_s }
49
+ end
50
+
51
+ ##
52
+ # Raises if the current version already exists.
53
+ #
54
+ # @return [void]
55
+ def check
56
+ if published?
57
+ raise Error, "#{configuration.gem_name} #{current_version} already exists on #{configuration.rubygems_host}."
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ ##
64
+ # Resolved Semverve configuration.
65
+ #
66
+ # @return [Semverve::ResolvedConfiguration]
67
+ attr_reader :configuration
68
+
69
+ ##
70
+ # Current gem version.
71
+ #
72
+ # @return [Semverve::SemanticVersion]
73
+ attr_reader :current_version
74
+
75
+ ##
76
+ # Published versions from the configured RubyGems-compatible host.
77
+ #
78
+ # @return [Array<Hash>]
79
+ def versions
80
+ response = self.class.http_getter.call(versions_uri)
81
+
82
+ case response.code.to_i
83
+ when 200
84
+ parse_versions(response.body)
85
+ when 404
86
+ []
87
+ else
88
+ raise Error, "Could not check #{configuration.gem_name} on #{configuration.rubygems_host}: HTTP #{response.code}."
89
+ end
90
+ rescue JSON::ParserError => error
91
+ raise Error, "Could not parse published versions for #{configuration.gem_name}: #{error.message}"
92
+ rescue SystemCallError, Timeout::Error, SocketError, URI::Error => error
93
+ raise Error, "Could not check #{configuration.gem_name} on #{configuration.rubygems_host}: #{error.message}"
94
+ end
95
+
96
+ ##
97
+ # Endpoint for a gem's published versions.
98
+ #
99
+ # @return [URI::HTTP, URI::HTTPS]
100
+ def versions_uri
101
+ URI("#{configuration.rubygems_host.chomp("/")}/api/v1/versions/#{URI.encode_www_form_component(configuration.gem_name)}.json")
102
+ end
103
+
104
+ ##
105
+ # Parsed version response.
106
+ #
107
+ # @param [String] body
108
+ #
109
+ # @return [Array<Hash>]
110
+ def parse_versions(body)
111
+ versions = JSON.parse(body)
112
+ return versions if versions.is_a?(Array)
113
+
114
+ raise Error, "Could not parse published versions for #{configuration.gem_name}: expected an array."
115
+ end
116
+ end
117
+ end
data/lib/semverve/task.rb CHANGED
@@ -4,6 +4,7 @@ require "rake"
4
4
 
5
5
  require_relative "../semverve"
6
6
  require_relative "generator"
7
+ require_relative "published_version"
7
8
  require_relative "semantic_version"
8
9
  require_relative "version_file"
9
10
  require_relative "version_code_references"
@@ -98,13 +99,19 @@ module Semverve
98
99
  end
99
100
 
100
101
  desc "Generate a version.rb file"
101
- task :generate do
102
- puts "Generated #{Generator.new(Semverve.configuration.resolved).generate}"
102
+ task :generate, [:version, :format, :force] do |_task, args|
103
+ generator_options = generate_options(args)
104
+ puts "Generated #{Generator.new(
105
+ Semverve.configuration.resolved,
106
+ version: generator_options[:version],
107
+ format: generator_options[:format],
108
+ force: generator_options[:force]
109
+ ).generate}"
103
110
  end
104
111
 
105
- desc "Set the version.rb file to VERSION"
106
- task :set do
107
- set
112
+ desc "Set the version.rb file to version"
113
+ task :set, [:version] do |_task, args|
114
+ set(args)
108
115
  end
109
116
 
110
117
  desc "Check version references, code literals, and metadata"
@@ -127,6 +134,16 @@ module Semverve
127
134
  task :metadata do
128
135
  check_metadata
129
136
  end
137
+
138
+ desc "Check whether the current gem version is already published"
139
+ task :rubygems do
140
+ check_rubygems
141
+ end
142
+
143
+ desc "Check configured release-readiness surfaces"
144
+ task :release do
145
+ check_release
146
+ end
130
147
  end
131
148
 
132
149
  desc "Fix version references, code literals, and metadata"
@@ -169,14 +186,14 @@ module Semverve
169
186
  end
170
187
 
171
188
  ##
172
- # Sets the version to the value from the +VERSION+ environment variable.
189
+ # Sets the version to the value from the Rake +version+ argument.
190
+ #
191
+ # @param [Rake::TaskArguments] args
173
192
  #
174
193
  # @return [void]
175
- def set
194
+ def set(args)
176
195
  configuration = Semverve.configuration.resolved
177
- requested_version = SemanticVersion.parse(
178
- ENV.fetch("VERSION") { raise Error, "Set VERSION=MAJOR.MINOR.PATCH." }
179
- )
196
+ requested_version = SemanticVersion.parse(requested_version_argument(args))
180
197
  update = VersionFile.new(configuration).set(requested_version)
181
198
 
182
199
  report(update, configuration)
@@ -211,7 +228,7 @@ module Semverve
211
228
  def check_references
212
229
  configuration, current_version = check_context
213
230
  report_findings(
214
- [["version reference", VersionReferences.new(configuration, current_version).findings]],
231
+ [["version reference", VersionReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]],
215
232
  current_version,
216
233
  clean_message: "Version references are current."
217
234
  )
@@ -236,7 +253,7 @@ module Semverve
236
253
  def check_code
237
254
  configuration, current_version = check_context
238
255
  report_findings(
239
- [["code version literal", VersionCodeReferences.new(configuration, current_version).findings]],
256
+ [["code version literal", VersionCodeReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]],
240
257
  current_version,
241
258
  clean_message: "Code version literals are current."
242
259
  )
@@ -267,6 +284,31 @@ module Semverve
267
284
  )
268
285
  end
269
286
 
287
+ ##
288
+ # Checks whether the current gem version is already published.
289
+ #
290
+ # @return [void]
291
+ def check_rubygems
292
+ configuration, current_version = check_context
293
+ PublishedVersion.new(configuration, current_version).check
294
+
295
+ puts "#{configuration.gem_name} #{current_version} is not published on #{configuration.rubygems_host}."
296
+ end
297
+
298
+ ##
299
+ # Checks configured release-readiness surfaces.
300
+ #
301
+ # @return [void]
302
+ def check_release
303
+ configuration, current_version = check_context
304
+
305
+ if configuration.release_checks.include?(:rubygems)
306
+ PublishedVersion.new(configuration, current_version).check
307
+ end
308
+
309
+ puts "Release checks passed."
310
+ end
311
+
270
312
  ##
271
313
  # Fixes safe gem metadata version mismatches.
272
314
  #
@@ -298,10 +340,10 @@ module Semverve
298
340
  def check_groups(configuration, current_version)
299
341
  groups = []
300
342
  if configuration.version_checks.include?(:doc_references)
301
- groups << ["version reference", VersionReferences.new(configuration, current_version).findings]
343
+ groups << ["version reference", VersionReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]
302
344
  end
303
345
  if configuration.version_checks.include?(:code_references)
304
- groups << ["code version literal", VersionCodeReferences.new(configuration, current_version).findings]
346
+ groups << ["code version literal", VersionCodeReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]
305
347
  end
306
348
  if configuration.version_checks.include?(:metadata)
307
349
  groups << [nil, VersionMetadata.new(configuration, current_version).findings]
@@ -309,6 +351,67 @@ module Semverve
309
351
  groups
310
352
  end
311
353
 
354
+ ##
355
+ # Whether check tasks should report references hidden by ignore markers.
356
+ #
357
+ # @return [Boolean]
358
+ def report_ignored?
359
+ ENV.fetch("SEMVERVE_REPORT_IGNORED", "false").match?(/\A(true|1|yes)\z/i)
360
+ end
361
+
362
+ ##
363
+ # Parsed arguments for +semverve:generate+.
364
+ #
365
+ # @param [Rake::TaskArguments] args
366
+ #
367
+ # @return [Hash]
368
+ def generate_options(args)
369
+ args.to_a.each_with_object({force: false}) do |value, options|
370
+ assign_generate_option(value, options)
371
+ end
372
+ end
373
+
374
+ ##
375
+ # Assigns a single +semverve:generate+ token by meaning.
376
+ #
377
+ # @param [String] value
378
+ # @param [Hash] options
379
+ #
380
+ # @return [void]
381
+ def assign_generate_option(value, options)
382
+ case value
383
+ when nil, ""
384
+ nil
385
+ when "force"
386
+ raise Error, "Duplicate generate option force." if options[:force]
387
+
388
+ options[:force] = true
389
+ when SemanticVersion::PATTERN
390
+ raise Error, "Duplicate generate version #{value.inspect}." if options[:version]
391
+
392
+ options[:version] = value
393
+ when "module", "simple"
394
+ raise Error, "Duplicate generate format #{value.inspect}." if options[:format]
395
+
396
+ options[:format] = value
397
+ else
398
+ raise Error, "Unknown generate option #{value.inspect}. Use a semantic version, module, simple, or force."
399
+ end
400
+ end
401
+
402
+ ##
403
+ # Required version argument for +semverve:set+.
404
+ #
405
+ # @param [Rake::TaskArguments] args
406
+ #
407
+ # @return [String]
408
+ def requested_version_argument(args)
409
+ version = args[:version]
410
+ raise Error, "Run rake 'semverve:set[MAJOR.MINOR.PATCH]'." if version.nil? || version.empty?
411
+
412
+ version
413
+ end
414
+
312
415
  ##
313
416
  # Fix results enabled for the umbrella fix task.
314
417
  #
@@ -15,13 +15,13 @@ module Semverve
15
15
  # Minor version.
16
16
  #
17
17
  # @return [Integer]
18
- MINOR = 1
18
+ MINOR = 2
19
19
 
20
20
  ##
21
21
  # Patch version.
22
22
  #
23
23
  # @return [Integer]
24
- PATCH = 0
24
+ PATCH = 1
25
25
 
26
26
  module_function
27
27
 
@@ -8,6 +8,12 @@ module Semverve
8
8
  ##
9
9
  # Finds and updates safe version literals in configured code files.
10
10
  class VersionCodeReferences
11
+ ##
12
+ # Marker that suppresses version-reference findings.
13
+ #
14
+ # @return [String]
15
+ IGNORE_MARKER = "semverve:ignore-version-reference"
16
+
11
17
  ##
12
18
  # Ruby assignments that are safe enough to rewrite automatically.
13
19
  #
@@ -93,9 +99,10 @@ module Semverve
93
99
  # @param [Semverve::SemanticVersion] current_version
94
100
  #
95
101
  # @return [Semverve::VersionCodeReferences]
96
- def initialize(configuration, current_version)
102
+ def initialize(configuration, current_version, include_ignored: false)
97
103
  @configuration = configuration
98
104
  @current_version = current_version
105
+ @include_ignored = include_ignored
99
106
  end
100
107
 
101
108
  ##
@@ -142,6 +149,12 @@ module Semverve
142
149
  # @return [Semverve::SemanticVersion]
143
150
  attr_reader :current_version
144
151
 
152
+ ##
153
+ # Whether ignored code literals should be included in findings.
154
+ #
155
+ # @return [Boolean]
156
+ attr_reader :include_ignored
157
+
145
158
  ##
146
159
  # Absolute configured project root.
147
160
  #
@@ -176,7 +189,11 @@ module Semverve
176
189
  #
177
190
  # @return [Array<Semverve::VersionCodeReferences::Finding>]
178
191
  def findings_for_file(path)
179
- File.readlines(path).filter_map.with_index(1) do |line, line_number|
192
+ lines = File.readlines(path)
193
+
194
+ lines.filter_map.with_index(1) do |line, line_number|
195
+ next if !include_ignored && ignored_line?(line_number, lines)
196
+
180
197
  match = line.match(pattern)
181
198
  next unless match
182
199
 
@@ -200,8 +217,11 @@ module Semverve
200
217
  # @return [Array(String, Integer)]
201
218
  def fixed_content(content)
202
219
  replacement_count = 0
220
+ lines = content.lines
221
+
222
+ fixed = lines.map.with_index(1) do |line, line_number|
223
+ next line if ignored_line?(line_number, lines)
203
224
 
204
- fixed = content.lines.map do |line|
205
225
  match = line.match(pattern)
206
226
  next line unless match
207
227
 
@@ -215,6 +235,23 @@ module Semverve
215
235
  [fixed, replacement_count]
216
236
  end
217
237
 
238
+ ##
239
+ # Whether findings on a line should be ignored.
240
+ #
241
+ # @param [Integer] line_number
242
+ # @param [Array<String>] lines
243
+ #
244
+ # @return [Boolean]
245
+ def ignored_line?(line_number, lines)
246
+ return true if lines.fetch(line_number - 1).include?(IGNORE_MARKER)
247
+
248
+ # Ruby 3.2 and 3.3 do not support Array#rfind.
249
+ # rubocop:disable Style/ReverseFind
250
+ previous_nonblank_line = lines[0...(line_number - 1)].reverse_each.find { |candidate| !candidate.strip.empty? }
251
+ # rubocop:enable Style/ReverseFind
252
+ previous_nonblank_line&.include?(IGNORE_MARKER)
253
+ end
254
+
218
255
  ##
219
256
  # Replaces only the named version capture in the first pattern match.
220
257
  #
@@ -107,9 +107,10 @@ module Semverve
107
107
  # @param [Semverve::SemanticVersion] current_version
108
108
  #
109
109
  # @return [Semverve::VersionReferences]
110
- def initialize(configuration, current_version)
110
+ def initialize(configuration, current_version, include_ignored: false)
111
111
  @configuration = configuration
112
112
  @current_version = current_version
113
+ @include_ignored = include_ignored
113
114
  end
114
115
 
115
116
  ##
@@ -156,6 +157,12 @@ module Semverve
156
157
  # @return [Semverve::SemanticVersion]
157
158
  attr_reader :current_version
158
159
 
160
+ ##
161
+ # Whether ignored references should be included in findings.
162
+ #
163
+ # @return [Boolean]
164
+ attr_reader :include_ignored
165
+
159
166
  ##
160
167
  # Absolute configured project root.
161
168
  #
@@ -346,7 +353,7 @@ module Semverve
346
353
  #
347
354
  # @return [Array<Hash>]
348
355
  def references_for_segment(segment, content)
349
- return [] if ignored_line?(segment[:line], content)
356
+ return [] if !include_ignored && ignored_line?(segment[:line], content)
350
357
 
351
358
  segment[:text].to_enum(:scan, VERSION_PATTERN).filter_map do
352
359
  match = Regexp.last_match
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: semverve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
@@ -119,6 +119,7 @@ files:
119
119
  - lib/semverve/generator.rb
120
120
  - lib/semverve/presets.rb
121
121
  - lib/semverve/project_metadata.rb
122
+ - lib/semverve/published_version.rb
122
123
  - lib/semverve/railtie.rb
123
124
  - lib/semverve/semantic_version.rb
124
125
  - lib/semverve/task.rb