gem_hadar 1.24.0 → 1.26.0
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 +4 -4
- data/README.md +225 -10
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/gem_hadar.gemspec +6 -5
- data/lib/gem_hadar/prompt_template.rb +91 -0
- data/lib/gem_hadar/setup.rb +4 -0
- data/lib/gem_hadar/template_compiler.rb +29 -0
- data/lib/gem_hadar/utils.rb +41 -0
- data/lib/gem_hadar/version.rb +1 -1
- data/lib/gem_hadar.rb +494 -104
- metadata +21 -3
data/lib/gem_hadar.rb
CHANGED
@@ -25,12 +25,16 @@ require_maybe 'rspec/core/rake_task'
|
|
25
25
|
class GemHadar
|
26
26
|
end
|
27
27
|
require 'gem_hadar/version'
|
28
|
+
require 'gem_hadar/utils'
|
28
29
|
require 'gem_hadar/setup'
|
29
30
|
require 'gem_hadar/template_compiler'
|
30
31
|
require 'gem_hadar/github'
|
32
|
+
require 'gem_hadar/prompt_template'
|
31
33
|
|
32
34
|
class GemHadar
|
33
35
|
include Term::ANSIColor
|
36
|
+
include GemHadar::Utils
|
37
|
+
include GemHadar::PromptTemplate
|
34
38
|
|
35
39
|
if defined?(::RbConfig)
|
36
40
|
include ::RbConfig
|
@@ -47,6 +51,12 @@ class GemHadar
|
|
47
51
|
block and instance_eval(&block)
|
48
52
|
end
|
49
53
|
|
54
|
+
# The has_to_be_set method raises an error if a required gem configuration
|
55
|
+
# attribute is not set.
|
56
|
+
#
|
57
|
+
# @param name [ String ] the name of the required attribute
|
58
|
+
#
|
59
|
+
# @raise [ ArgumentError ] if the specified attribute has not been set
|
50
60
|
def has_to_be_set(name)
|
51
61
|
fail "#{self.class}: #{name} has to be set for gem"
|
52
62
|
end
|
@@ -130,7 +140,7 @@ class GemHadar
|
|
130
140
|
end
|
131
141
|
|
132
142
|
dsl_accessor :yard_dir do
|
133
|
-
'
|
143
|
+
'doc'
|
134
144
|
end
|
135
145
|
|
136
146
|
dsl_accessor :extensions do FileList['ext/**/extconf.rb'] end
|
@@ -178,6 +188,18 @@ class GemHadar
|
|
178
188
|
dsl_accessor :gemset do @outer_scope.name end
|
179
189
|
end
|
180
190
|
|
191
|
+
# The rvm method configures RVM (Ruby Version Manager) settings for the gem
|
192
|
+
# project.
|
193
|
+
#
|
194
|
+
# This method initializes and returns an RvmConfig object that holds RVM-specific
|
195
|
+
# configuration such as the Ruby version to use and the gemset name.
|
196
|
+
# If a block is provided, it configures the RvmConfig object with the given
|
197
|
+
# settings. If no block is provided and no existing RvmConfig object exists,
|
198
|
+
# it creates a new one with default settings.
|
199
|
+
#
|
200
|
+
# @param block [ Proc ] optional block to configure RVM settings
|
201
|
+
#
|
202
|
+
# @return [ GemHadar::RvmConfig ] the RVM configuration object
|
181
203
|
def rvm(&block)
|
182
204
|
if block
|
183
205
|
@rvm = RvmConfig.new(&block)
|
@@ -189,6 +211,12 @@ class GemHadar
|
|
189
211
|
|
190
212
|
dsl_accessor :default_task_dependencies, [ :gemspec, :test ]
|
191
213
|
|
214
|
+
# The default_task method defines the default Rake task for the gem project.
|
215
|
+
#
|
216
|
+
# This method sets up a Rake task named :default that depends on the tasks
|
217
|
+
# specified in the default_task_dependencies accessor. It provides a convenient
|
218
|
+
# way to run the most common or essential tasks for the project with a single
|
219
|
+
# command.
|
192
220
|
def default_task
|
193
221
|
desc 'Default task'
|
194
222
|
task :default => default_task_dependencies
|
@@ -196,11 +224,26 @@ class GemHadar
|
|
196
224
|
|
197
225
|
dsl_accessor :build_task_dependencies, [ :clobber, :gemspec, :package, :'version:tag' ]
|
198
226
|
|
227
|
+
# The build_task method defines a Rake task that orchestrates the complete
|
228
|
+
# build process for packaging the gem.
|
229
|
+
#
|
230
|
+
# This method sets up a :build task that depends on the tasks specified in
|
231
|
+
# the build_task_dependencies accessor. It provides a convenient way to
|
232
|
+
# execute all necessary steps for building packages for a release with a
|
233
|
+
# single command.
|
199
234
|
def build_task
|
200
235
|
desc 'Build task (builds all packages for a release)'
|
201
236
|
task :build => build_task_dependencies
|
202
237
|
end
|
203
238
|
|
239
|
+
# The install_library method sets up a Rake task for installing the library
|
240
|
+
# or executable into site_ruby directories.
|
241
|
+
#
|
242
|
+
# This method configures an :install task that depends on the
|
243
|
+
# :prepare_install task and executes the provided block. It stores the block
|
244
|
+
# in an instance variable to be called later when the task is executed.
|
245
|
+
#
|
246
|
+
# @param block [ Proc ] the block containing the installation logic
|
204
247
|
def install_library(&block)
|
205
248
|
@install_library_block = -> do
|
206
249
|
desc 'Install executable/library into site_ruby directories'
|
@@ -208,6 +251,15 @@ class GemHadar
|
|
208
251
|
end
|
209
252
|
end
|
210
253
|
|
254
|
+
# The clean method manages the CLEAN file list for Rake tasks.
|
255
|
+
#
|
256
|
+
# When called without arguments, it returns the current CLEAN file list.
|
257
|
+
# When called with arguments, it adds the specified files to the CLEAN list.
|
258
|
+
#
|
259
|
+
# @param args [ Array<String> ] optional list of files to add to the CLEAN list
|
260
|
+
#
|
261
|
+
# @return [ FileList, nil ] the CLEAN file list when no arguments provided,
|
262
|
+
# nil otherwise
|
211
263
|
def clean(*args)
|
212
264
|
if args.empty?
|
213
265
|
CLEAN
|
@@ -216,6 +268,15 @@ class GemHadar
|
|
216
268
|
end
|
217
269
|
end
|
218
270
|
|
271
|
+
# The clobber method manages the CLOBBER file list for Rake tasks.
|
272
|
+
#
|
273
|
+
# When called without arguments, it returns the current CLOBBER file list.
|
274
|
+
# When called with arguments, it adds the specified files to the CLOBBER list.
|
275
|
+
#
|
276
|
+
# @param args [ Array<String> ] optional list of files to add to the CLOBBER list
|
277
|
+
#
|
278
|
+
# @return [ FileList, nil ] the CLOBBER file list when no arguments provided,
|
279
|
+
# nil otherwise
|
219
280
|
def clobber(*args)
|
220
281
|
if args.empty?
|
221
282
|
CLOBBER
|
@@ -224,6 +285,15 @@ class GemHadar
|
|
224
285
|
end
|
225
286
|
end
|
226
287
|
|
288
|
+
# The ignore method manages the list of files to be ignored by the gem.
|
289
|
+
#
|
290
|
+
# When called without arguments, it returns the current set of ignored files.
|
291
|
+
# When called with arguments, it adds the specified files to the ignore list.
|
292
|
+
#
|
293
|
+
# @param args [ Array<String> ] optional list of file patterns to add to the ignore list
|
294
|
+
#
|
295
|
+
# @return [ Set<String>, nil ] the set of ignored files when no arguments provided,
|
296
|
+
# nil otherwise
|
227
297
|
def ignore(*args)
|
228
298
|
if args.empty?
|
229
299
|
ignore_files
|
@@ -232,6 +302,17 @@ class GemHadar
|
|
232
302
|
end
|
233
303
|
end
|
234
304
|
|
305
|
+
# The package_ignore method manages the list of files to be ignored during
|
306
|
+
# gem packaging.
|
307
|
+
#
|
308
|
+
# When called without arguments, it returns the current set of package ignore
|
309
|
+
# files. When called with arguments, it adds the specified file patterns to
|
310
|
+
# the package ignore list.
|
311
|
+
#
|
312
|
+
# @param args [ Array<String> ] optional list of file patterns to add to the package ignore list
|
313
|
+
#
|
314
|
+
# @return [ Set<String>, nil ] the set of package ignore files when no arguments provided,
|
315
|
+
# nil otherwise
|
235
316
|
def package_ignore(*args)
|
236
317
|
if args.empty?
|
237
318
|
package_ignore_files
|
@@ -240,14 +321,28 @@ class GemHadar
|
|
240
321
|
end
|
241
322
|
end
|
242
323
|
|
324
|
+
# The dependency method adds a new runtime dependency to the gem.
|
325
|
+
#
|
326
|
+
# @param args [ Array ] the arguments defining the dependency
|
243
327
|
def dependency(*args)
|
244
328
|
@dependencies << args
|
245
329
|
end
|
246
330
|
|
331
|
+
# The development_dependency method adds a new development-time dependency to
|
332
|
+
# the gem.
|
333
|
+
#
|
334
|
+
# @param args [ Array ] the arguments defining the development dependency
|
247
335
|
def development_dependency(*args)
|
248
336
|
@development_dependencies << args
|
249
337
|
end
|
250
338
|
|
339
|
+
# The gems_install_task method defines a Rake task for installing all gem
|
340
|
+
# dependencies specified in the Gemfile.
|
341
|
+
#
|
342
|
+
# This method sets up a :gems:install task that executes a block to install
|
343
|
+
# gems. If no block is provided, it defaults to running 'bundle install'.
|
344
|
+
#
|
345
|
+
# @param block [ Proc ] optional block containing the installation command
|
251
346
|
def gems_install_task(&block)
|
252
347
|
block ||= proc { sh 'bundle install' }
|
253
348
|
desc 'Install all gems from the Gemfile'
|
@@ -256,6 +351,14 @@ class GemHadar
|
|
256
351
|
end
|
257
352
|
end
|
258
353
|
|
354
|
+
# The version_task method defines a Rake task that generates a version file
|
355
|
+
# for the gem.
|
356
|
+
#
|
357
|
+
# This method creates a task named :version that writes version information
|
358
|
+
# to a Ruby file in the lib directory. The generated file contains constants
|
359
|
+
# for the version and its components, as well as an optional epilogue
|
360
|
+
# section. The task ensures the target directory exists and uses secure file
|
361
|
+
# writing to prevent permission issues.
|
259
362
|
def version_task
|
260
363
|
desc m = "Writing version information for #{name}-#{version}"
|
261
364
|
task :version do
|
@@ -277,6 +380,13 @@ class GemHadar
|
|
277
380
|
end
|
278
381
|
end
|
279
382
|
|
383
|
+
# The version_show_task method defines a Rake task that displays the current
|
384
|
+
# version of the gem.
|
385
|
+
#
|
386
|
+
# This method creates a :version:show task under the Rake namespace that
|
387
|
+
# reads the version from the generated version file in the lib directory and
|
388
|
+
# compares it with the version specified in the GemHadar configuration. It
|
389
|
+
# then outputs a message indicating whether the versions match or not.
|
280
390
|
def version_show_task
|
281
391
|
namespace :version do
|
282
392
|
desc "Displaying the current version"
|
@@ -295,6 +405,21 @@ class GemHadar
|
|
295
405
|
end
|
296
406
|
end
|
297
407
|
|
408
|
+
# The version_log_diff method generates a git log output containing patch
|
409
|
+
# differences between two specified versions.
|
410
|
+
#
|
411
|
+
# This method retrieves the commit history between a starting version and an
|
412
|
+
# ending version, including detailed changes (patch format) for each commit.
|
413
|
+
# It supports comparing against the current HEAD or specific version tags,
|
414
|
+
# and automatically determines the appropriate previous version when only a
|
415
|
+
# target version is provided.
|
416
|
+
#
|
417
|
+
# @param to_version [ String ] the ending version tag or 'HEAD' to compare up to the latest commit
|
418
|
+
# @param from_version [ String, nil ] the starting version tag; if nil, it defaults based on to_version
|
419
|
+
#
|
420
|
+
# @raise [ RuntimeError ] if the specified version tags are not found in the repository
|
421
|
+
#
|
422
|
+
# @return [ String ] the git log output in patch format showing changes between the two versions
|
298
423
|
def version_log_diff(to_version: 'HEAD', from_version: nil)
|
299
424
|
if to_version == 'HEAD'
|
300
425
|
if from_version.blank?
|
@@ -327,6 +452,15 @@ class GemHadar
|
|
327
452
|
end
|
328
453
|
end
|
329
454
|
|
455
|
+
# The version_diff_task method defines Rake tasks for listing and displaying
|
456
|
+
# git version differences.
|
457
|
+
#
|
458
|
+
# This method sets up two subtasks under the :version namespace:
|
459
|
+
#
|
460
|
+
# - A :list task that fetches all git tags, ensures the operation succeeds,
|
461
|
+
# and outputs the sorted list of versions.
|
462
|
+
# - A :diff task that calculates the version range, displays a colored diff
|
463
|
+
# between the versions, and shows the changes.
|
330
464
|
def version_diff_task
|
331
465
|
namespace :version do
|
332
466
|
desc "List all versions in order"
|
@@ -338,19 +472,25 @@ class GemHadar
|
|
338
472
|
|
339
473
|
desc "Displaying the diff from env var VERSION to the next version or HEAD"
|
340
474
|
task :diff do
|
341
|
-
|
342
|
-
found_version_tag = version_tags.index(version_tag(version))
|
343
|
-
found_version_tag.nil? and fail "cannot find version tag #{version_tag(version)}"
|
344
|
-
start_version, end_version = version_tags[found_version_tag, 2]
|
475
|
+
start_version, end_version = determine_version_range
|
345
476
|
puts color(172) { "Showing diff from version %s to %s:" % [ start_version, end_version ] }
|
346
477
|
puts `git diff --color=always #{start_version}..#{end_version}`
|
347
478
|
end
|
348
479
|
end
|
349
480
|
end
|
350
481
|
|
482
|
+
# The gem_hadar_update_task method defines a Rake task that updates the
|
483
|
+
# gem_hadar dependency version in the gemspec file.
|
484
|
+
#
|
485
|
+
# This method creates a :gem_hadar:update task under the Rake namespace that
|
486
|
+
# prompts the user to specify a new gem_hadar version.
|
487
|
+
# It then reads the existing gemspec file, modifies the version constraint
|
488
|
+
# for the gem_hadar dependency, and writes the updated content back to the
|
489
|
+
# file. If the specified version is already present in the gemspec, it skips
|
490
|
+
# the update and notifies the user.
|
351
491
|
def gem_hadar_update_task
|
352
492
|
namespace :gem_hadar do
|
353
|
-
desc 'Update gem_hadar a different version'
|
493
|
+
desc 'Update gem_hadar to a different version'
|
354
494
|
task :update do
|
355
495
|
answer = ask?("Which gem_hadar version? ", /^((?:\d+.){2}(?:\d+))$/)
|
356
496
|
unless answer
|
@@ -376,6 +516,14 @@ class GemHadar
|
|
376
516
|
end
|
377
517
|
end
|
378
518
|
|
519
|
+
# The gemspec_task method defines a Rake task that generates and writes a
|
520
|
+
# gemspec file for the project.
|
521
|
+
#
|
522
|
+
# This method creates a :gemspec task that depends on the :version task,
|
523
|
+
# ensuring the version is set before generating the gemspec. It constructs
|
524
|
+
# the filename based on the project name, displays a warning message
|
525
|
+
# indicating the file being written, and uses secure_write to create the
|
526
|
+
# gemspec file with content generated by the gemspec method.
|
379
527
|
def gemspec_task
|
380
528
|
desc 'Create a gemspec file'
|
381
529
|
task :gemspec => :version do
|
@@ -385,6 +533,12 @@ class GemHadar
|
|
385
533
|
end
|
386
534
|
end
|
387
535
|
|
536
|
+
# The package_task method sets up a Rake task for packaging the gem.
|
537
|
+
#
|
538
|
+
# This method configures a task that creates a package directory, initializes
|
539
|
+
# a Gem::PackageTask with the current gem specification, and specifies that
|
540
|
+
# tar files should be created. It also includes the files to be packaged by
|
541
|
+
# adding gem_files to the package_files attribute of the Gem::PackageTask.
|
388
542
|
def package_task
|
389
543
|
clean 'pkg'
|
390
544
|
Gem::PackageTask.new(gemspec) do |pkg|
|
@@ -393,24 +547,20 @@ class GemHadar
|
|
393
547
|
end
|
394
548
|
end
|
395
549
|
|
550
|
+
# The install_library_task method executes the installed library task block
|
551
|
+
# if it has been defined.
|
396
552
|
def install_library_task
|
397
553
|
@install_library_block.full?(:call)
|
398
554
|
end
|
399
555
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
end
|
409
|
-
cmd << ' - ' << doc_files * ' '
|
410
|
-
sh cmd
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
556
|
+
# The test_task method sets up a Rake task for executing the project's test
|
557
|
+
# suite.
|
558
|
+
#
|
559
|
+
# This method configures a Rake task named :test that runs the test suite
|
560
|
+
# using Rake::TestTask. It includes the test directory and required paths in
|
561
|
+
# the load path, specifies the test files to run, and enables verbose output.
|
562
|
+
# The task also conditionally depends on the :compile task if project
|
563
|
+
# extensions are present.
|
414
564
|
def test_task
|
415
565
|
tt = Rake::TestTask.new(:run_tests) do |t|
|
416
566
|
t.libs << test_dir
|
@@ -422,6 +572,12 @@ class GemHadar
|
|
422
572
|
task :test => [ (:compile if extensions.full?), tt.name ].compact
|
423
573
|
end
|
424
574
|
|
575
|
+
# The spec_task method sets up a Rake task for executing RSpec tests.
|
576
|
+
#
|
577
|
+
# This method configures a :spec task that runs the project's RSpec test
|
578
|
+
# suite. It initializes an RSpec::Core::RakeTask with appropriate Ruby
|
579
|
+
# options, test pattern, and verbose output. The task also conditionally
|
580
|
+
# depends on the :compile task if project extensions are present.
|
425
581
|
def spec_task
|
426
582
|
defined? ::RSpec::Core::RakeTask or return
|
427
583
|
st = RSpec::Core::RakeTask.new(:run_specs) do |t|
|
@@ -433,6 +589,15 @@ class GemHadar
|
|
433
589
|
task :spec => [ (:compile if extensions.full?), st.name ].compact
|
434
590
|
end
|
435
591
|
|
592
|
+
# The rcov_task method sets up a Rake task for executing code coverage tests
|
593
|
+
# using RCov.
|
594
|
+
#
|
595
|
+
# This method configures a :rcov task that runs the project's test suite with
|
596
|
+
# RCov to generate code coverage reports. It includes the test directory and
|
597
|
+
# required paths in the load path, specifies the test files to run, and
|
598
|
+
# enables verbose output. The task also conditionally depends on the :compile
|
599
|
+
# task if project extensions are present. If RCov is not available, it
|
600
|
+
# displays a warning message suggesting to install RCov.
|
436
601
|
def rcov_task
|
437
602
|
if defined?(::Rcov)
|
438
603
|
rt = ::Rcov::RcovTask.new(:run_rcov) do |t|
|
@@ -454,33 +619,76 @@ class GemHadar
|
|
454
619
|
end
|
455
620
|
end
|
456
621
|
|
622
|
+
# The version_bump_task method defines Rake tasks for incrementing the gem's
|
623
|
+
# version number.
|
624
|
+
#
|
625
|
+
# This method sets up a hierarchical task structure under the :version
|
626
|
+
# namespace:
|
627
|
+
#
|
628
|
+
# - It creates subtasks in the :version:bump namespace for explicitly bumping
|
629
|
+
# major, minor, or build versions.
|
630
|
+
# - It also defines a :version:bump task that automatically suggests the
|
631
|
+
# appropriate version bump type by analyzing recent changes using AI. The
|
632
|
+
# suggestion is based on the git log diff between the previous version and
|
633
|
+
# the current HEAD, and it prompts the user for confirmation before applying
|
634
|
+
# the bump.
|
635
|
+
#
|
636
|
+
# The tasks utilize the version_log_diff method to gather change information,
|
637
|
+
# the ollama_generate method to get AI-powered suggestions, and the
|
638
|
+
# version_bump_to method to perform the actual version update.
|
457
639
|
def version_bump_task
|
458
640
|
namespace :version do
|
459
641
|
namespace :bump do
|
460
642
|
desc 'Bump major version'
|
461
643
|
task :major do
|
462
|
-
|
463
|
-
version.bump(:major)
|
464
|
-
secure_write('VERSION') { |v| v.puts version }
|
644
|
+
version_bump_to(:major)
|
465
645
|
end
|
466
646
|
|
467
647
|
desc 'Bump minor version'
|
468
648
|
task :minor do
|
469
|
-
|
470
|
-
version.bump(:minor)
|
471
|
-
secure_write('VERSION') { |v| v.puts version }
|
649
|
+
version_bump_to(:minor)
|
472
650
|
end
|
473
651
|
|
474
652
|
desc 'Bump build version'
|
475
653
|
task :build do
|
476
|
-
|
477
|
-
|
478
|
-
|
654
|
+
version_bump_to(:build)
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
desc 'Bump version with AI suggestion'
|
659
|
+
task :bump do
|
660
|
+
log_diff = version_log_diff(from_version: nil, to_version: 'HEAD')
|
661
|
+
system = xdg_config('version_bump_system_prompt.txt', default_version_bump_system_prompt)
|
662
|
+
prompt = xdg_config('version_bump_prompt.txt', default_version_bump_prompt) % { version:, log_diff: }
|
663
|
+
response = ollama_generate(system:, prompt:)
|
664
|
+
puts response
|
665
|
+
default = nil
|
666
|
+
if response =~ /(major|minor|build)\s*$/
|
667
|
+
default = $1
|
668
|
+
end
|
669
|
+
response = ask?(
|
670
|
+
'Bump a major, minor, or build version%{default}? ',
|
671
|
+
/\A(major|minor|build)\z/,
|
672
|
+
default:
|
673
|
+
)
|
674
|
+
if version_type = response&.[](1)
|
675
|
+
version_bump_to(version_type)
|
676
|
+
else
|
677
|
+
exit 1
|
479
678
|
end
|
480
679
|
end
|
481
680
|
end
|
482
681
|
end
|
483
682
|
|
683
|
+
# The version_tag_task method defines a Rake task that creates a Git tag for
|
684
|
+
# the current version.
|
685
|
+
#
|
686
|
+
# This method sets up a :version:tag task under the Rake namespace that
|
687
|
+
# creates an annotated Git tag for the project's current version. It checks
|
688
|
+
# if a tag with the same name already exists and handles the case where the
|
689
|
+
# tag exists but is different from the current commit. If the tag already
|
690
|
+
# exists and is different, it prompts the user to confirm whether to
|
691
|
+
# overwrite it forcefully.
|
484
692
|
def version_tag_task
|
485
693
|
namespace :version do
|
486
694
|
desc "Tag this commit as version #{version}"
|
@@ -505,10 +713,24 @@ class GemHadar
|
|
505
713
|
end
|
506
714
|
end
|
507
715
|
|
716
|
+
# The git_remote method retrieves the primary Git remote name configured for
|
717
|
+
# the project.
|
718
|
+
#
|
719
|
+
# It first checks the GIT_REMOTE environment variable for a custom remote
|
720
|
+
# specification. If not set, it defaults to 'origin'. When multiple remotes
|
721
|
+
# are specified in the environment variable, only the first one is returned.
|
508
722
|
def git_remote
|
509
723
|
ENV.fetch('GIT_REMOTE', 'origin').split(/\s+/).first
|
510
724
|
end
|
511
725
|
|
726
|
+
# The master_prepare_task method defines a Rake task that sets up a remote
|
727
|
+
# Git repository for the project.
|
728
|
+
#
|
729
|
+
# This method creates a :master:prepare task under the Rake namespace that
|
730
|
+
# guides the user through creating a new bare Git repository on a remote
|
731
|
+
# server via SSH. It prompts for the remote name, directory path, and SSH
|
732
|
+
# account details to configure the repository and establish a connection back
|
733
|
+
# to the local project.
|
512
734
|
def master_prepare_task
|
513
735
|
namespace :master do
|
514
736
|
desc "Prepare a remote git repository for this project"
|
@@ -526,6 +748,20 @@ class GemHadar
|
|
526
748
|
end
|
527
749
|
end
|
528
750
|
|
751
|
+
# The version_push_task method defines Rake tasks for pushing version tags to
|
752
|
+
# Git remotes.
|
753
|
+
#
|
754
|
+
# This method sets up a hierarchical task structure under the :version
|
755
|
+
# namespace:
|
756
|
+
#
|
757
|
+
# - It creates subtasks in the :version:push namespace for each configured
|
758
|
+
# Git remote, allowing individual pushes to specific remotes.
|
759
|
+
# - It also defines a top-level :version:push task that depends on all the
|
760
|
+
# individual remote push tasks, enabling a single command to push the version
|
761
|
+
# tag to all remotes.
|
762
|
+
#
|
763
|
+
# The tasks utilize the git_remotes method to determine which remotes are
|
764
|
+
# configured and generate appropriate push commands for each one.
|
529
765
|
def version_push_task
|
530
766
|
namespace :version do
|
531
767
|
git_remotes.each do |gr|
|
@@ -542,6 +778,19 @@ class GemHadar
|
|
542
778
|
end
|
543
779
|
end
|
544
780
|
|
781
|
+
# The master_push_task method defines Rake tasks for pushing the master
|
782
|
+
# branch to configured Git remotes.
|
783
|
+
#
|
784
|
+
# This method sets up a hierarchical task structure under the :master namespace:
|
785
|
+
#
|
786
|
+
# - It creates subtasks in the :master:push namespace for each configured Git
|
787
|
+
# remote, allowing individual pushes to specific remotes.
|
788
|
+
# - It also defines a top-level :master:push task that depends on all the individual
|
789
|
+
# remote push tasks, enabling a single command to push the master branch to
|
790
|
+
# all remotes.
|
791
|
+
#
|
792
|
+
# The tasks utilize the git_remotes method to determine which remotes are
|
793
|
+
# configured and generate appropriate push commands for each one.
|
545
794
|
def master_push_task
|
546
795
|
namespace :master do
|
547
796
|
git_remotes.each do |gr|
|
@@ -558,6 +807,17 @@ class GemHadar
|
|
558
807
|
end
|
559
808
|
end
|
560
809
|
|
810
|
+
# The gem_push_task method defines a Rake task for pushing the generated gem
|
811
|
+
# file to RubyGems.
|
812
|
+
#
|
813
|
+
# This method sets up a :gem:push task under the Rake namespace that handles
|
814
|
+
# the process of uploading the gem package to RubyGems. It checks if the
|
815
|
+
# project is in developing mode and skips the push operation if so.
|
816
|
+
# Otherwise, it verifies the existence of the gem file, prompts the user for
|
817
|
+
# confirmation before pushing, and uses the gem push command with an optional
|
818
|
+
# API key from the environment. If the gem file does not exist or the user
|
819
|
+
# declines to push, appropriate messages are displayed and the task exits
|
820
|
+
# accordingly.
|
561
821
|
def gem_push_task
|
562
822
|
namespace :gem do
|
563
823
|
path = "pkg/#{name_version}.gem"
|
@@ -586,6 +846,15 @@ class GemHadar
|
|
586
846
|
end
|
587
847
|
end
|
588
848
|
|
849
|
+
# The git_remotes_task method defines a Rake task that displays all Git
|
850
|
+
# remote repositories configured for the project.
|
851
|
+
#
|
852
|
+
# This method sets up a :git_remotes task under the Rake namespace that
|
853
|
+
# retrieves and prints the list of Git remotes along with their URLs. It uses
|
854
|
+
# the git_remotes method to obtain the remote names and then fetches each
|
855
|
+
# remote's URL using the `git remote get-url` command. The output is
|
856
|
+
# formatted to show each remote name followed by its corresponding URL on
|
857
|
+
# separate lines.
|
589
858
|
def git_remotes_task
|
590
859
|
task :git_remotes do
|
591
860
|
puts git_remotes.map { |r|
|
@@ -595,65 +864,29 @@ class GemHadar
|
|
595
864
|
end
|
596
865
|
end
|
597
866
|
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
867
|
+
# The create_git_release_body method generates a changelog for a GitHub
|
868
|
+
# release by analyzing the git diff between the previous version and the
|
869
|
+
# current HEAD.
|
870
|
+
#
|
871
|
+
# It retrieves the log differences, fetches or uses default system and prompt
|
872
|
+
# templates, and utilizes an AI model to produce a formatted changelog entry.
|
873
|
+
#
|
874
|
+
# @return [ String ] the generated changelog content for the release body
|
875
|
+
def create_git_release_body
|
604
876
|
log_diff = version_log_diff(to_version: version)
|
605
|
-
|
606
|
-
|
607
|
-
system
|
608
|
-
You are a Ruby programmer generating changelog messages in markdown
|
609
|
-
format for new releases, so users can see what has changed. Remember you
|
610
|
-
are not a chatbot of any kind.
|
611
|
-
EOT
|
612
|
-
prompt = (<<~EOT) % { name:, version:, log_diff: }
|
613
|
-
Output the content of a changelog for the new release of %{name} %{version}
|
614
|
-
|
615
|
-
**Strictly** follow these guidelines:
|
616
|
-
|
617
|
-
- Use bullet points in markdown format (`-`) to list significant changes.
|
618
|
-
- Exclude trivial updates such as:
|
619
|
-
* Version number increments
|
620
|
-
* Dependency version bumps (unless they resolve critical issues)
|
621
|
-
* Minor code style adjustments
|
622
|
-
* Internal documentation tweaks
|
623
|
-
- Include only verified and substantial changes that impact
|
624
|
-
functionality, performance, or user experience.
|
625
|
-
- If unsure about a change's significance, omit it from the output.
|
626
|
-
- Avoid adding any comments or notes; keep the output purely factual.
|
627
|
-
|
628
|
-
These are the log messages including patches for the new release:
|
629
|
-
|
630
|
-
%{log_diff}
|
631
|
-
EOT
|
632
|
-
options = ENV['OLLAMA_OPTIONS'].full? { |o| JSON.parse(o) } || {}
|
633
|
-
options |= { "temperature" => 0, "top_p" => 1, "min_p" => 0.1 }
|
634
|
-
ollama.generate(model:, system:, prompt:, options:, stream: false, think: false).response
|
635
|
-
end
|
636
|
-
|
637
|
-
def edit_temp_file(content)
|
638
|
-
editor = ENV.fetch('EDITOR', `which vi`.chomp)
|
639
|
-
unless File.exist?(editor)
|
640
|
-
warn "Can't find EDITOR. => Returning."
|
641
|
-
return
|
642
|
-
end
|
643
|
-
temp_file = Tempfile.new('changelog')
|
644
|
-
temp_file.write(content)
|
645
|
-
temp_file.close
|
646
|
-
|
647
|
-
unless system("#{editor} #{temp_file.path}")
|
648
|
-
warn "#{editor} returned #{$?.exitstatus} => Returning."
|
649
|
-
return
|
650
|
-
end
|
651
|
-
|
652
|
-
File.read(temp_file.path)
|
653
|
-
ensure
|
654
|
-
temp_file&.close&.unlink
|
877
|
+
system = xdg_config('release_system_prompt.txt', default_git_release_system_prompt)
|
878
|
+
prompt = xdg_config('release_prompt.txt', default_git_release_prompt) % { name:, version:, log_diff: }
|
879
|
+
ollama_generate(system:, prompt:)
|
655
880
|
end
|
656
881
|
|
882
|
+
# The github_release_task method defines a Rake task that creates a GitHub
|
883
|
+
# release for the current version.
|
884
|
+
#
|
885
|
+
# This method sets up a :github:release task that prompts the user to confirm
|
886
|
+
# publishing a release message on GitHub. It retrieves the GitHub API token
|
887
|
+
# from the environment, derives the repository owner and name from the git
|
888
|
+
# remote URL, generates a changelog using AI, and creates the release via the
|
889
|
+
# GitHub API.
|
657
890
|
def github_release_task
|
658
891
|
namespace :github do
|
659
892
|
unless github_api_token = ENV['GITHUB_API_TOKEN'].full?
|
@@ -661,7 +894,7 @@ class GemHadar
|
|
661
894
|
task :release
|
662
895
|
return
|
663
896
|
end
|
664
|
-
desc "Create a new GitHub release for the current version with a changelog"
|
897
|
+
desc "Create a new GitHub release for the current version with a AI-generated changelog"
|
665
898
|
task :release do
|
666
899
|
yes = ask?(
|
667
900
|
"Do you want to publish a release message on github? (y/n%{default}) ",
|
@@ -675,7 +908,7 @@ class GemHadar
|
|
675
908
|
rc = GitHub::ReleaseCreator.new(owner:, repo:, token: github_api_token)
|
676
909
|
tag_name = version_tag(version)
|
677
910
|
target_commitish = `git show -s --format=%H #{tag_name.inspect}^{commit}`.chomp
|
678
|
-
body = edit_temp_file(
|
911
|
+
body = edit_temp_file(create_git_release_body)
|
679
912
|
if body.present?
|
680
913
|
begin
|
681
914
|
response = rc.perform(tag_name:, target_commitish:, body:)
|
@@ -695,6 +928,14 @@ class GemHadar
|
|
695
928
|
|
696
929
|
dsl_accessor :push_task_dependencies, %i[ modified build master:push version:push gem:push github:release ]
|
697
930
|
|
931
|
+
# The push_task method defines a Rake task that orchestrates the complete
|
932
|
+
# process of pushing changes and artifacts to remote repositories and package
|
933
|
+
# managers.
|
934
|
+
#
|
935
|
+
# This method sets up multiple subtasks including preparing the master
|
936
|
+
# branch, pushing version tags, pushing to gem repositories, and creating
|
937
|
+
# GitHub releases. It also includes a check for uncommitted changes before
|
938
|
+
# proceeding with the push operations.
|
698
939
|
def push_task
|
699
940
|
master_prepare_task
|
700
941
|
version_push_task
|
@@ -709,10 +950,26 @@ class GemHadar
|
|
709
950
|
exit 1
|
710
951
|
end
|
711
952
|
end
|
712
|
-
desc "Push
|
953
|
+
desc "Push all changes for version #{version} into the internets."
|
713
954
|
task :push => push_task_dependencies
|
714
955
|
end
|
715
956
|
|
957
|
+
# The release_task method defines a Rake task that orchestrates the complete
|
958
|
+
# release process for the gem.
|
959
|
+
#
|
960
|
+
# This method sets up a :release task that depends on the :push task,
|
961
|
+
# ensuring all necessary steps for publishing the gem are executed in
|
962
|
+
# sequence. It provides a convenient way to perform a full release workflow
|
963
|
+
# with a single command.
|
964
|
+
def release_task
|
965
|
+
desc "Release the new version #{version} for the gem #{name}."
|
966
|
+
task :release => :push
|
967
|
+
end
|
968
|
+
|
969
|
+
# The compile_task method sets up a Rake task to compile project extensions.
|
970
|
+
#
|
971
|
+
# This method creates a :compile task that iterates through the configured
|
972
|
+
# extensions and compiles them using the system's make command.
|
716
973
|
def compile_task
|
717
974
|
for file in extensions
|
718
975
|
dir = File.dirname(file)
|
@@ -730,6 +987,16 @@ class GemHadar
|
|
730
987
|
end
|
731
988
|
end
|
732
989
|
|
990
|
+
# The rvm_task method creates a .rvmrc file that configures RVM to use the
|
991
|
+
# specified Ruby version and gemset for the project.
|
992
|
+
#
|
993
|
+
# This task generates a .rvmrc file in the project root directory with commands to:
|
994
|
+
# - Use the Ruby version specified by the rvm.use accessor
|
995
|
+
# - Create the gemset specified by the rvm.gemset accessor
|
996
|
+
# - Switch to using that gemset
|
997
|
+
#
|
998
|
+
# The generated file is written using the secure_write method to ensure
|
999
|
+
# proper file permissions.
|
733
1000
|
def rvm_task
|
734
1001
|
desc 'Create .rvmrc file'
|
735
1002
|
task :rvm do
|
@@ -743,15 +1010,55 @@ class GemHadar
|
|
743
1010
|
end
|
744
1011
|
end
|
745
1012
|
|
1013
|
+
def yard_doc_task
|
1014
|
+
YARD::Rake::YardocTask.new(:yard_doc) do |t|
|
1015
|
+
t.files = files.select { _1 =~ /\.rb\z/ }
|
1016
|
+
|
1017
|
+
output_dir = yard_dir
|
1018
|
+
t.options = [ "--output-dir=#{output_dir}" ]
|
1019
|
+
|
1020
|
+
# Include private & protected methods in documentation
|
1021
|
+
t.options << '--private' << '--protected'
|
1022
|
+
|
1023
|
+
# Handle readme if present
|
1024
|
+
if readme && File.exist?(readme)
|
1025
|
+
t.options << "--readme=#{readme}"
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
# Add additional documentation files
|
1029
|
+
if doc_files&.any?
|
1030
|
+
t.files.concat(doc_files.flatten)
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
# Add before hook for cleaning
|
1034
|
+
t.before = proc {
|
1035
|
+
clean output_dir
|
1036
|
+
puts "Generating full documentation in #{output_dir}..."
|
1037
|
+
}
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# The yard_task method sets up and registers Rake tasks for generating and
|
1042
|
+
# managing YARD documentation.
|
1043
|
+
#
|
1044
|
+
# It creates multiple subtasks under the :yard namespace, including tasks for
|
1045
|
+
# creating private documentation, viewing the generated documentation,
|
1046
|
+
# cleaning up documentation files, and listing undocumented elements.
|
1047
|
+
# If YARD is not available, the method returns early without defining any tasks.
|
746
1048
|
def yard_task
|
747
1049
|
defined? YARD or return
|
1050
|
+
yard_doc_task
|
1051
|
+
desc 'Create yard documentation (including private)'
|
1052
|
+
task :doc => :yard_doc
|
748
1053
|
namespace :yard do
|
749
1054
|
my_yard_dir = Pathname.new(yard_dir)
|
750
1055
|
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
1056
|
+
task :private => :yard_doc
|
1057
|
+
|
1058
|
+
task :public => :yard_doc
|
1059
|
+
|
1060
|
+
desc 'Create yard documentation'
|
1061
|
+
task :doc => :yard_doc
|
755
1062
|
|
756
1063
|
desc 'View the yard documentation'
|
757
1064
|
task :view do
|
@@ -789,7 +1096,6 @@ class GemHadar
|
|
789
1096
|
gem_hadar_update_task
|
790
1097
|
gemspec_task
|
791
1098
|
gems_install_task
|
792
|
-
doc_task
|
793
1099
|
if test_dir
|
794
1100
|
test_task
|
795
1101
|
rcov_task
|
@@ -801,6 +1107,7 @@ class GemHadar
|
|
801
1107
|
version_bump_task
|
802
1108
|
version_tag_task
|
803
1109
|
push_task
|
1110
|
+
release_task
|
804
1111
|
write_ignore_file
|
805
1112
|
write_gemfile
|
806
1113
|
if extensions.full?
|
@@ -812,6 +1119,77 @@ class GemHadar
|
|
812
1119
|
self
|
813
1120
|
end
|
814
1121
|
|
1122
|
+
# The edit_temp_file method creates a temporary file with the provided
|
1123
|
+
# content, opens it in an editor for user modification, and returns the
|
1124
|
+
# updated content.
|
1125
|
+
#
|
1126
|
+
# @param content [ String ] the initial content to write to the temporary file
|
1127
|
+
def edit_temp_file(content)
|
1128
|
+
editor = ENV.fetch('EDITOR', `which vi`.chomp)
|
1129
|
+
unless File.exist?(editor)
|
1130
|
+
warn "Can't find EDITOR. => Returning."
|
1131
|
+
return
|
1132
|
+
end
|
1133
|
+
temp_file = Tempfile.new('changelog')
|
1134
|
+
temp_file.write(content)
|
1135
|
+
temp_file.close
|
1136
|
+
|
1137
|
+
unless system("#{editor} #{temp_file.path}")
|
1138
|
+
warn "#{editor} returned #{$?.exitstatus} => Returning."
|
1139
|
+
return
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
File.read(temp_file.path)
|
1143
|
+
ensure
|
1144
|
+
temp_file&.close&.unlink
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
# Generates a response from an AI model using the Ollama::Client.
|
1148
|
+
#
|
1149
|
+
# @param [String] system The system prompt for the AI model.
|
1150
|
+
# @param [String] prompt The user prompt to generate a response to.
|
1151
|
+
# @return [String, nil] The generated response or nil if generation fails.
|
1152
|
+
def ollama_generate(system:, prompt:)
|
1153
|
+
base_url = ENV['OLLAMA_URL']
|
1154
|
+
if base_url.blank? && host = ENV['OLLAMA_HOST'].full?
|
1155
|
+
base_url = 'http://%s' % host
|
1156
|
+
end
|
1157
|
+
base_url.present? or return
|
1158
|
+
ollama = Ollama::Client.new(base_url:, read_timeout: 600, connect_timeout: 60)
|
1159
|
+
model = ENV.fetch('OLLAMA_MODEL', 'llama3.1')
|
1160
|
+
options = ENV['OLLAMA_OPTIONS'].full? { |o| JSON.parse(o) } || {}
|
1161
|
+
options |= { "temperature" => 0, "top_p" => 1, "min_p" => 0.1 }
|
1162
|
+
ollama.generate(model:, system:, prompt:, options:, stream: false, think: false).response
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
# Increases the specified part of the version number and writes it back to
|
1166
|
+
# the VERSION file.
|
1167
|
+
#
|
1168
|
+
# @param [Symbol, String] type The part of the version to bump (:major, :minor, or :build)
|
1169
|
+
def version_bump_to(type)
|
1170
|
+
type = type.to_sym
|
1171
|
+
version = File.read('VERSION').chomp.version
|
1172
|
+
version.bump(type)
|
1173
|
+
secure_write('VERSION') { |v| v.puts version }
|
1174
|
+
exit 0
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
# Determine the start and end versions for diff comparison.
|
1178
|
+
#
|
1179
|
+
# If the VERSION env var is set, it will be used as the starting version tag.
|
1180
|
+
# Otherwise, it defaults to the current commit's version or the latest tag.
|
1181
|
+
#
|
1182
|
+
# @return [Array(String, String)] A fixed-size array containing:
|
1183
|
+
# - The start version (e.g., '1.2.3') from which changes are compared.
|
1184
|
+
# - The end version (e.g., '1.2.4' or 'HEAD') up to which changes are compared.
|
1185
|
+
def determine_version_range
|
1186
|
+
version_tags = versions.map { version_tag(_1) } + %w[ HEAD ]
|
1187
|
+
found_version_tag = version_tags.index(version_tag(version))
|
1188
|
+
found_version_tag.nil? and fail "cannot find version tag #{version_tag(version)}"
|
1189
|
+
start_version, end_version = version_tags[found_version_tag, 2]
|
1190
|
+
return start_version, end_version
|
1191
|
+
end
|
1192
|
+
|
815
1193
|
# The write_ignore_file method writes the current ignore_files configuration
|
816
1194
|
# to a .gitignore file in the project root directory.
|
817
1195
|
def write_ignore_file
|
@@ -909,8 +1287,12 @@ class GemHadar
|
|
909
1287
|
s.rdoc_options << '--title' << "#{name.camelize} - #{summary}"
|
910
1288
|
end
|
911
1289
|
if readme
|
912
|
-
|
913
|
-
|
1290
|
+
if File.exist?(readme)
|
1291
|
+
s.rdoc_options << '--main' << readme
|
1292
|
+
s.extra_rdoc_files << readme
|
1293
|
+
else
|
1294
|
+
warn "Add a #{readme} file to document your gem!"
|
1295
|
+
end
|
914
1296
|
end
|
915
1297
|
doc_files.full? { |df| s.extra_rdoc_files.concat Array(df) }
|
916
1298
|
end
|
@@ -934,12 +1316,6 @@ class GemHadar
|
|
934
1316
|
end
|
935
1317
|
super(*args)
|
936
1318
|
end
|
937
|
-
def fail(*args)
|
938
|
-
args.map! do |a|
|
939
|
-
a.respond_to?(:to_str) ? color(196) { a.to_str } : a
|
940
|
-
end
|
941
|
-
super(*args)
|
942
|
-
end
|
943
1319
|
|
944
1320
|
# The git_remotes method retrieves the list of remote repositories configured
|
945
1321
|
# for the current Git project.
|
@@ -999,19 +1375,33 @@ class GemHadar
|
|
999
1375
|
#
|
1000
1376
|
# @return [ Array<String> ] an array of version strings sorted in ascending
|
1001
1377
|
# order according to semantic versioning rules.
|
1378
|
+
memoize method:
|
1002
1379
|
def versions
|
1003
|
-
|
1380
|
+
`git tag`.lines.grep(/^v?\d+\.\d+\.\d+$/).map(&:chomp).map {
|
1004
1381
|
_1.sub(/\Av/, '')
|
1005
1382
|
}.sort_by(&:version)
|
1006
1383
|
end
|
1007
1384
|
|
1008
1385
|
# The version_tag method prepends a 'v' prefix to the given version
|
1009
|
-
# string.
|
1386
|
+
# string, unless it's HEAD.
|
1010
1387
|
#
|
1011
1388
|
# @param version [String] the version string to modify
|
1012
1389
|
# @return [String] the modified version string with a 'v' prefix
|
1013
1390
|
def version_tag(version)
|
1014
|
-
version
|
1391
|
+
if version != 'HEAD'
|
1392
|
+
version.dup.prepend ?v
|
1393
|
+
else
|
1394
|
+
version.dup
|
1395
|
+
end
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
# The version_untag method removes the 'v' prefix from a version tag string.
|
1399
|
+
#
|
1400
|
+
# @param version_tag [ String ] the version tag string that may start with 'v'
|
1401
|
+
#
|
1402
|
+
# @return [ String ] the version string with the 'v' prefix removed
|
1403
|
+
def version_untag(version_tag)
|
1404
|
+
version.sub(/\Av/, '')
|
1015
1405
|
end
|
1016
1406
|
|
1017
1407
|
# The github_remote_url method retrieves and parses the GitHub remote URL
|