gem_hadar 1.20.0 → 1.21.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: 1ead4ce8736282232d2dc21c166eb42ee97c6782592c730ef3b309056aef5945
4
- data.tar.gz: b7caac8b3c428ec8560844fa2b4f1d10a2c21bab4075591a1c5353804c3e8720
3
+ metadata.gz: 1b87e2b5371fa3f1260204c5d71a43d793516d5d734f7b3f0ef3846f696d8b98
4
+ data.tar.gz: 3ea0ff7dc2ea379f270716b8d46dda9fe7901ebaaadf1e76ee713324674fb262
5
5
  SHA512:
6
- metadata.gz: 7aafadc3e8ae5bf2ea948cf89479deff500bd353e0290176230b511eec34490429df2cb1146570f621876a2c51f591fdfe3817fce0ccdd85db7d21da3b805200
7
- data.tar.gz: d3fced27d7affae79ef190d2d2072a30c96dc5129ab00743792ae65000d171651beac5656de1d1292b88ec371fb5704d227f5b49141082634b9b586be9047a2f
6
+ metadata.gz: d0ca8414e965de5954624b7d2467317fe7dedff4578ea1f7ce8d6e7c9eb9c95693cc65907172dce03ed58678f0bdd0e2a961709e91154d3e3a92a95cfa530bca
7
+ data.tar.gz: 492ed0f4757edf87d56f4b10074a76eff4220dc43b7f78e696bf7f1abfa1ebc986439aefc7de1825c893c1a98f7bbd4dc8c981873d92ecb7ce1e49e339858bc4
data/Rakefile CHANGED
@@ -17,6 +17,7 @@ GemHadar do
17
17
 
18
18
  dependency 'tins', '~> 1.0'
19
19
  dependency 'term-ansicolor', '~> 1.0'
20
+ dependency 'ollama-ruby', '~> 1.0'
20
21
  dependency 'rake'
21
22
  dependency 'yard'
22
23
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.20.0
1
+ 1.21.1
data/gem_hadar.gemspec CHANGED
@@ -1,30 +1,31 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: gem_hadar 1.20.0 ruby lib
2
+ # stub: gem_hadar 1.21.1 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "gem_hadar".freeze
6
- s.version = "1.20.0".freeze
6
+ s.version = "1.21.1".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Florian Frank".freeze]
11
- s.date = "2025-02-19"
11
+ s.date = "1980-01-02"
12
12
  s.description = "This library contains some useful functionality to support the development of Ruby Gems".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["gem_hadar".freeze]
15
- s.extra_rdoc_files = ["README.md".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/version.rb".freeze]
16
- s.files = [".gitignore".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/gem_hadar".freeze, "gem_hadar.gemspec".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/version.rb".freeze]
15
+ s.extra_rdoc_files = ["README.md".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/github.rb".freeze, "lib/gem_hadar/setup.rb".freeze, "lib/gem_hadar/template_compiler.rb".freeze, "lib/gem_hadar/version.rb".freeze]
16
+ s.files = [".gitignore".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/gem_hadar".freeze, "gem_hadar.gemspec".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/github.rb".freeze, "lib/gem_hadar/setup.rb".freeze, "lib/gem_hadar/template_compiler.rb".freeze, "lib/gem_hadar/version.rb".freeze]
17
17
  s.homepage = "https://github.com/flori/gem_hadar".freeze
18
18
  s.licenses = ["MIT".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "GemHadar - Library for the development of Ruby Gems".freeze, "--main".freeze, "README.md".freeze]
20
- s.rubygems_version = "3.6.2".freeze
20
+ s.rubygems_version = "3.6.9".freeze
21
21
  s.summary = "Library for the development of Ruby Gems".freeze
22
22
 
23
23
  s.specification_version = 4
24
24
 
25
- s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.19".freeze])
25
+ s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.21".freeze])
26
26
  s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.0".freeze])
27
27
  s.add_runtime_dependency(%q<term-ansicolor>.freeze, ["~> 1.0".freeze])
28
+ s.add_runtime_dependency(%q<ollama-ruby>.freeze, ["~> 1.0".freeze])
28
29
  s.add_runtime_dependency(%q<rake>.freeze, [">= 0".freeze])
29
30
  s.add_runtime_dependency(%q<yard>.freeze, [">= 0".freeze])
30
31
  end
@@ -0,0 +1,62 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module GemHadar::GitHub
5
+ end
6
+
7
+ class GemHadar::GitHub::ReleaseCreator
8
+ class << self
9
+ attr_accessor :github_api_url
10
+ end
11
+ self.github_api_url = 'https://api.github.com'
12
+
13
+ def initialize(owner:, repo:, token:, api_version: '2022-11-28')
14
+ @owner = owner
15
+ @repo = repo
16
+ @token = token
17
+ @api_version = api_version
18
+ end
19
+
20
+ def perform(tag_name:, target_commitish:, body:, name: tag_name, draft: false, prerelease: false)
21
+ uri = URI("#{self.class.github_api_url}/repos/#{@owner}/#{@repo}/releases")
22
+
23
+ headers = {
24
+ "Accept" => "application/vnd.github+json",
25
+ "Authorization" => "Bearer #{@token}",
26
+ "X-GitHub-Api-Version" => @api_version
27
+ }
28
+
29
+ data = {
30
+ tag_name:,
31
+ target_commitish:,
32
+ body:,
33
+ name:,
34
+ draft:,
35
+ prerelease:,
36
+ }.compact
37
+
38
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
39
+ req.body = JSON(data)
40
+
41
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
42
+ http.request(req)
43
+ end
44
+
45
+ case response
46
+ when Net::HTTPSuccess
47
+ JSON.parse(response.body, object_class: JSON::GenericObject)
48
+ else
49
+ error_data =
50
+ begin
51
+ JSON.pretty_generate(JSON.parse(response.body))
52
+ rescue
53
+ response.body
54
+ end
55
+ error_msg = "Failed to create release. Status: #{response.code}\n\n#{error_data}"
56
+ raise error_msg
57
+ end
58
+ rescue => e
59
+ warn "Error creating release: #{e.message}"
60
+ nil
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ class GemHadar::Setup
2
+ include FileUtils
3
+
4
+ def perform
5
+ mkdir_p 'lib'
6
+ unless File.exist?('VERSION')
7
+ File.open('VERSION', 'w') do |output|
8
+ output.puts '0.0.0'
9
+ end
10
+ end
11
+ unless File.exist?('Rakefile')
12
+ File.open('Rakefile', 'w') do |output|
13
+ output.puts <<~EOT
14
+ # vim: set filetype=ruby et sw=2 ts=2:
15
+
16
+ require 'gem_hadar'
17
+
18
+ GemHadar do
19
+ #developing true
20
+ #name 'TODO'
21
+ module_type :class
22
+ #author 'TODO'
23
+ #email 'todo@example.com'
24
+ #homepage "https://github.com/TODO/NAME"
25
+ #summary 'TODO'
26
+ description 'TODO'
27
+ test_dir 'spec'
28
+ ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.AppleDouble', '.bundle', '.yardoc', 'tags'
29
+ readme 'README.md'
30
+
31
+ #executables << 'bin/TODO'
32
+
33
+ #dependency 'TODO', '~>1.2.3'
34
+
35
+ #licenses << 'TODO
36
+ end
37
+ EOT
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ require 'erb'
2
+
3
+ class GemHadar::TemplateCompiler
4
+ include Tins::BlockSelf
5
+ include Tins::MethodMissingDelegator::DelegatorModule
6
+
7
+ def initialize(&block)
8
+ super block_self(&block)
9
+ @values = {}
10
+ instance_eval(&block)
11
+ end
12
+
13
+ def compile(src, dst)
14
+ template = File.read(src)
15
+ File.open(dst, 'w') do |output|
16
+ erb = ERB.new(template, nil, '-')
17
+ erb.filename = src.to_s
18
+ output.write erb.result binding
19
+ end
20
+ end
21
+
22
+ def method_missing(id, *a, &b)
23
+ if a.empty? && id && @values.key?(id)
24
+ @values[id]
25
+ elsif a.size == 1
26
+ @values[id] = a.first
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
@@ -1,6 +1,6 @@
1
1
  class GemHadar
2
2
  # GemHadar version
3
- VERSION = '1.20.0'
3
+ VERSION = '1.21.1'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/gem_hadar.rb CHANGED
@@ -15,18 +15,19 @@ require 'rake/testtask'
15
15
  require 'dslkit/polite'
16
16
  require 'set'
17
17
  require 'pathname'
18
- require 'erb'
19
- require 'gem_hadar/version'
18
+ require 'ollama'
20
19
  require 'term/ansicolor'
21
20
  require_maybe 'yard'
22
21
  require_maybe 'simplecov'
23
22
  require_maybe 'rubygems/package_task'
24
23
  require_maybe 'rcov/rcovtask'
25
24
  require_maybe 'rspec/core/rake_task'
26
-
27
- def GemHadar(&block)
28
- GemHadar.new(&block).create_all_tasks
25
+ class GemHadar
29
26
  end
27
+ require 'gem_hadar/version'
28
+ require 'gem_hadar/setup'
29
+ require 'gem_hadar/template_compiler'
30
+ require 'gem_hadar/github'
30
31
 
31
32
  class GemHadar
32
33
  include Term::ANSIColor
@@ -50,18 +51,6 @@ class GemHadar
50
51
  fail "#{self.class}: #{name} has to be set for gem"
51
52
  end
52
53
 
53
- def assert_valid_link(name, orig_url)
54
- developing and return orig_url
55
- url = orig_url
56
- begin
57
- response = Net::HTTP.get_response(URI.parse(url))
58
- url = response['location']
59
- end while response.is_a?(Net::HTTPRedirection)
60
- response.is_a?(Net::HTTPOK) or
61
- fail "#{orig_url.inspect} for #{name} has to be a valid link"
62
- orig_url
63
- end
64
-
65
54
  dsl_accessor :developing, false
66
55
 
67
56
  dsl_accessor :name do
@@ -214,7 +203,7 @@ class GemHadar
214
203
  end
215
204
 
216
205
  def install_library(&block)
217
- @install_library_block = lambda do
206
+ @install_library_block = -> do
218
207
  desc 'Install executable/library into site_ruby directories'
219
208
  task :install => :prepare_install, &block
220
209
  end
@@ -268,72 +257,22 @@ class GemHadar
268
257
  end
269
258
  end
270
259
 
271
- def gem_files
272
- (files.to_a - package_ignore_files.to_a)
273
- end
274
-
275
- def gemspec
276
- Gem::Specification.new do |s|
277
- s.name = name
278
- s.version = ::Gem::Version.new(version)
279
- s.author = author
280
- s.email = email
281
- s.homepage = assert_valid_link(:homepage, homepage)
282
- s.summary = summary
283
- s.description = description
284
-
285
- gem_files.full? { |f| s.files = Array(f) }
286
- test_files.full? { |t| s.test_files = Array(t) }
287
- extensions.full? { |e| s.extensions = Array(e) }
288
- bindir.full? { |b| s.bindir = b }
289
- executables.full? { |e| s.executables = Array(e) }
290
- licenses.full? { |l| s.licenses = Array(licenses) }
291
- post_install_message.full? { |m| s.post_install_message = m }
292
-
293
- required_ruby_version.full? { |v| s.required_ruby_version = v }
294
- s.add_development_dependency('gem_hadar', "~> #{VERSION[/\A\d+\.\d+/, 0]}")
295
- for d in @development_dependencies
296
- s.add_development_dependency(*d)
297
- end
298
- for d in @dependencies
299
- if s.respond_to?(:add_runtime_dependency)
300
- s.add_runtime_dependency(*d)
301
- else
302
- s.add_dependency(*d)
303
- end
304
- end
305
-
306
- require_paths.full? { |r| s.require_paths = Array(r) }
307
-
308
- if title
309
- s.rdoc_options << '--title' << title
310
- else
311
- s.rdoc_options << '--title' << "#{name.camelize} - #{summary}"
312
- end
313
- if readme
314
- s.rdoc_options << '--main' << readme
315
- s.extra_rdoc_files << readme
316
- end
317
- doc_files.full? { |df| s.extra_rdoc_files.concat Array(df) }
318
- end
319
- end
320
-
321
260
  def version_task
322
261
  desc m = "Writing version information for #{name}-#{version}"
323
262
  task :version do
324
263
  puts m
325
264
  mkdir_p dir = File.join('lib', path_name)
326
265
  secure_write(File.join(dir, 'version.rb')) do |v|
327
- v.puts <<EOT
328
- #{module_type} #{path_module}
329
- # #{path_module} version
330
- VERSION = '#{version}'
331
- VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
332
- VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
333
- VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
334
- VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
335
- end
336
- EOT
266
+ v.puts <<~EOT
267
+ #{module_type} #{path_module}
268
+ # #{path_module} version
269
+ VERSION = '#{version}'
270
+ VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
271
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
272
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
273
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
274
+ end
275
+ EOT
337
276
  version_epilogue.full? { |ve| v.puts ve }
338
277
  end
339
278
  end
@@ -357,10 +296,36 @@ EOT
357
296
  end
358
297
  end
359
298
 
360
- def versions
361
- `git tag`.lines.grep(/^v?\d+\.\d+\.\d+$/).map(&:chomp).map {
362
- _1.sub(/\Av/, '')
363
- }.sort_by(&:version)
299
+ def version_log_diff(to_version: 'HEAD', from_version: nil)
300
+ if to_version == 'HEAD'
301
+ if from_version.blank?
302
+ from_version = versions.last
303
+ else
304
+ unless versions.find { |v| v == from_version }
305
+ fail "Could not find #{from_version.inspect}."
306
+ end
307
+ end
308
+ `git log -p #{version_identifier(from_version)}..HEAD`
309
+ else
310
+ unless versions.find { |v| v == to_version }
311
+ fail "Could not find #{to_version.inspect}."
312
+ end
313
+ if from_version.blank?
314
+ from_version = versions.each_cons(2).find do |previous_version, v|
315
+ if v == to_version
316
+ break previous_version
317
+ end
318
+ end
319
+ unless from_version
320
+ fail "Could not find version before #{to_version.inspect}."
321
+ end
322
+ else
323
+ unless versions.find { |v| v == from_version }
324
+ fail "Could not find #{from_version.inspect}."
325
+ end
326
+ end
327
+ `git log -p #{version_identifier(from_version)}..#{version_identifier(to_version)}`
328
+ end
364
329
  end
365
330
 
366
331
  def version_diff_task
@@ -374,8 +339,8 @@ EOT
374
339
 
375
340
  desc "Displaying the diff from env var VERSION to the next version or HEAD"
376
341
  task :diff do
377
- arg_version = ENV.fetch('VERSION', version).dup.prepend(?v)
378
- my_versions = versions.map { _1.prepend(?v) } + %w[ HEAD ]
342
+ arg_version = version_identifier(ENV.fetch('VERSION', version))
343
+ my_versions = versions.map { version_identifier(_1) } + %w[ HEAD ]
379
344
  start_version, end_version = my_versions[my_versions.index(arg_version), 2]
380
345
  puts color(172) {"Showing diff from version %s to %s:" % [ start_version, end_version ]}
381
346
  puts `git diff --color=always #{start_version}..#{end_version}`
@@ -489,41 +454,6 @@ EOT
489
454
  end
490
455
  end
491
456
 
492
- def self.start_simplecov
493
- defined? SimpleCov or return
494
- filter = "#{File.basename(File.dirname(caller.first))}/"
495
- SimpleCov.start do
496
- add_filter filter
497
- end
498
- end
499
-
500
- def write_ignore_file
501
- secure_write('.gitignore') do |output|
502
- output.puts(ignore.sort)
503
- end
504
- end
505
-
506
- def write_gemfile
507
- default_gemfile =<<EOT
508
- # vim: set filetype=ruby et sw=2 ts=2:
509
-
510
- source 'https://rubygems.org'
511
-
512
- gemspec
513
- EOT
514
- current_gemfile = File.exist?('Gemfile') && File.read('Gemfile')
515
- case current_gemfile
516
- when false
517
- secure_write('Gemfile') do |output|
518
- output.write default_gemfile
519
- end
520
- when default_gemfile
521
- ;;
522
- else
523
- warn "INFO: Current Gemfile differs from default Gemfile."
524
- end
525
- end
526
-
527
457
  def version_bump_task
528
458
  namespace :version do
529
459
  namespace :bump do
@@ -557,7 +487,7 @@ EOT
557
487
  task :tag do
558
488
  force = ENV['FORCE'].to_i == 1
559
489
  begin
560
- sh "git tag -a -m 'Version #{version}' #{'-f' if force} v#{version}"
490
+ sh "git tag -a -m 'Version #{version}' #{'-f' if force} #{version_identifier(version)}"
561
491
  rescue RuntimeError
562
492
  if `git diff v#{version}..HEAD`.empty?
563
493
  puts "Version #{version} is already tagged, but it's no different"
@@ -579,12 +509,6 @@ EOT
579
509
  ENV.fetch('GIT_REMOTE', 'origin').split(/\s+/).first
580
510
  end
581
511
 
582
- def git_remotes
583
- remotes = ENV['GIT_REMOTE'].full?(:split, /\s+/)
584
- remotes or remotes = `git remote`.lines.map(&:chomp)
585
- remotes
586
- end
587
-
588
512
  def master_prepare_task
589
513
  namespace :master do
590
514
  desc "Prepare a remote git repository for this project"
@@ -671,12 +595,107 @@ EOT
671
595
  end
672
596
  end
673
597
 
598
+ def create_body
599
+ base_url = ENV['OLLAMA_URL']
600
+ if base_url.blank? && host = ENV['OLLAMA_HOST'].full?
601
+ base_url = 'http://%s' % host
602
+ end
603
+ base_url.present? or return
604
+ log_diff = version_log_diff(to_version: version)
605
+ model = ENV.fetch('OLLAMA_MODEL', 'llama3.1')
606
+ ollama = Ollama::Client.new(base_url:, read_timeout: 600, connect_timeout: 60)
607
+ system = <<~EOT
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
655
+ end
656
+
657
+ def github_release_task
658
+ namespace :github do
659
+ unless github_api_token = ENV['GITHUB_API_TOKEN'].full?
660
+ warn "GITHUB_API_TOKEN not set. => Skipping github release task."
661
+ task :release
662
+ return
663
+ end
664
+ desc "Create a new GitHub release for the current version with a changelog"
665
+ task :release do
666
+ yes = ask?(
667
+ "Do you want to publish a release message on github? (y/n%{default}) ",
668
+ /\Ay/i, default: ENV['GITHUB_RELEASE_ENABLED']
669
+ )
670
+ unless yes
671
+ warn "Skipping publication of a github release message."
672
+ next
673
+ end
674
+ if %r(\A/*(?<owner>[^/]+)/(?<repo>[^/.]+)) =~ github_remote_url&.path
675
+ rc = GitHub::ReleaseCreator.new(owner:, repo:, token: github_api_token)
676
+ tag_name = version_identifier(version)
677
+ target_commitish = `git rev-parse #{tag_name.inspect}`.chomp
678
+ body = edit_temp_file(create_body)
679
+ if body.present?
680
+ response = rc.perform(tag_name:, target_commitish:, body:)
681
+ puts "Release created successfully! See #{response['html_url']}"
682
+ else
683
+ warn "Skipping creation of github release message."
684
+ end
685
+ else
686
+ warn "Could not derive github remote url from git remotes. => Skipping github release task."
687
+ end
688
+ end
689
+ end
690
+ end
691
+
674
692
  def push_task
675
693
  master_prepare_task
676
694
  version_push_task
677
695
  master_push_task
678
696
  gem_push_task
679
697
  git_remotes_task
698
+ github_release_task
680
699
  task :modified do
681
700
  changed_files = `git status --porcelain`.gsub(/^\s*\S\s+/, '').lines
682
701
  unless changed_files.empty?
@@ -685,7 +704,7 @@ EOT
685
704
  end
686
705
  end
687
706
  desc "Push master and version #{version} all git remotes: #{git_remotes * ' '}"
688
- task :push => %i[ modified build master:push version:push gem:push ]
707
+ task :push => %i[ modified build master:push version:push gem:push github:release ]
689
708
  end
690
709
 
691
710
  def compile_task
@@ -709,11 +728,11 @@ EOT
709
728
  desc 'Create .rvmrc file'
710
729
  task :rvm do
711
730
  secure_write('.rvmrc') do |output|
712
- output.write <<EOT
713
- rvm use #{rvm.use}
714
- rvm gemset create #{rvm.gemset}
715
- rvm gemset use #{rvm.gemset}
716
- EOT
731
+ output.write <<~EOT
732
+ rvm use #{rvm.use}
733
+ rvm gemset create #{rvm.gemset}
734
+ rvm gemset use #{rvm.gemset}
735
+ EOT
717
736
  end
718
737
  end
719
738
  end
@@ -750,14 +769,10 @@ EOT
750
769
  task :yard => %i[ yard:private yard:view ]
751
770
  end
752
771
 
753
- def ask?(prompt, pattern)
754
- STDOUT.print prompt
755
- answer = STDIN.gets.chomp
756
- if answer =~ pattern
757
- $~
758
- end
759
- end
760
-
772
+ # The create_all_tasks method sets up and registers all the Rake tasks for
773
+ # the gem project.
774
+ #
775
+ # @return [GemHadar] the instance of GemHadar
761
776
  def create_all_tasks
762
777
  default_task
763
778
  build_task
@@ -791,77 +806,246 @@ EOT
791
806
  self
792
807
  end
793
808
 
794
- class TemplateCompiler
795
- include Tins::BlockSelf
796
- include Tins::MethodMissingDelegator::DelegatorModule
797
-
798
- def initialize(&block)
799
- super block_self(&block)
800
- @values = {}
801
- instance_eval(&block)
809
+ # The write_ignore_file method writes the current ignore_files configuration
810
+ # to a .gitignore file in the project root directory.
811
+ def write_ignore_file
812
+ secure_write('.gitignore') do |output|
813
+ output.puts(ignore.sort)
802
814
  end
815
+ end
816
+
817
+ # The write_gemfile method creates and writes the default Gemfile content if
818
+ # it doesn't exist. If a custom Gemfile exists, it only displays a warning.
819
+ def write_gemfile
820
+ default_gemfile =<<~EOT
821
+ # vim: set filetype=ruby et sw=2 ts=2:
803
822
 
804
- def compile(src, dst)
805
- template = File.read(src)
806
- File.open(dst, 'w') do |output|
807
- erb = ERB.new(template, nil, '-')
808
- erb.filename = src.to_s
809
- output.write erb.result binding
823
+ source 'https://rubygems.org'
824
+
825
+ gemspec
826
+ EOT
827
+ current_gemfile = File.exist?('Gemfile') && File.read('Gemfile')
828
+ case current_gemfile
829
+ when false
830
+ secure_write('Gemfile') do |output|
831
+ output.write default_gemfile
810
832
  end
833
+ when default_gemfile
834
+ ;;
835
+ else
836
+ warn "INFO: Current Gemfile differs from default Gemfile."
811
837
  end
838
+ end
839
+
840
+ # The assert_valid_link method verifies that the provided URL is valid by
841
+ # checking if it returns an HTTP OK status after following redirects, unless
842
+ # project is still `developing`.
843
+ #
844
+ # @param name [String] the name associated with the link being validated
845
+ # @param orig_url [String] the URL to validate
846
+ #
847
+ # @return [String] the original URL if validation succeeds
848
+ #
849
+ # @raise [ArgumentError] if the final response is not an HTTP OK status after
850
+ # following redirects
851
+ def assert_valid_link(name, orig_url)
852
+ developing and return orig_url
853
+ url = orig_url
854
+ begin
855
+ response = Net::HTTP.get_response(URI.parse(url))
856
+ url = response['location']
857
+ end while response.is_a?(Net::HTTPRedirection)
858
+ response.is_a?(Net::HTTPOK) or
859
+ fail "#{orig_url.inspect} for #{name} has to be a valid link"
860
+ orig_url
861
+ end
812
862
 
813
- def method_missing(id, *a, &b)
814
- if a.empty? && id && @values.key?(id)
815
- @values[id]
816
- elsif a.size == 1
817
- @values[id] = a.first
863
+ # The gemspec method creates and returns a new Gem::Specification object
864
+ # that defines the metadata and dependencies for the gem package.
865
+ #
866
+ # @return [Gem::Specification] a fully configured Gem specification object
867
+ def gemspec
868
+ Gem::Specification.new do |s|
869
+ s.name = name
870
+ s.version = ::Gem::Version.new(version)
871
+ s.author = author
872
+ s.email = email
873
+ s.homepage = assert_valid_link(:homepage, homepage)
874
+ s.summary = summary
875
+ s.description = description
876
+
877
+ gem_files.full? { |f| s.files = Array(f) }
878
+ test_files.full? { |t| s.test_files = Array(t) }
879
+ extensions.full? { |e| s.extensions = Array(e) }
880
+ bindir.full? { |b| s.bindir = b }
881
+ executables.full? { |e| s.executables = Array(e) }
882
+ licenses.full? { |l| s.licenses = Array(licenses) }
883
+ post_install_message.full? { |m| s.post_install_message = m }
884
+
885
+ required_ruby_version.full? { |v| s.required_ruby_version = v }
886
+ s.add_development_dependency('gem_hadar', "~> #{VERSION[/\A\d+\.\d+/, 0]}")
887
+ for d in @development_dependencies
888
+ s.add_development_dependency(*d)
889
+ end
890
+ for d in @dependencies
891
+ if s.respond_to?(:add_runtime_dependency)
892
+ s.add_runtime_dependency(*d)
893
+ else
894
+ s.add_dependency(*d)
895
+ end
896
+ end
897
+
898
+ require_paths.full? { |r| s.require_paths = Array(r) }
899
+
900
+ if title
901
+ s.rdoc_options << '--title' << title
818
902
  else
819
- super
903
+ s.rdoc_options << '--title' << "#{name.camelize} - #{summary}"
904
+ end
905
+ if readme
906
+ s.rdoc_options << '--main' << readme
907
+ s.extra_rdoc_files << readme
820
908
  end
909
+ doc_files.full? { |df| s.extra_rdoc_files.concat Array(df) }
821
910
  end
822
911
  end
823
912
 
824
- class Setup
825
- include FileUtils
913
+ # The warn method displays warning messages using orange colored output.
914
+ #
915
+ # @param msgs [Array<String>] the array of message strings to display
916
+ def warn(*msgs)
917
+ msgs.map! { |m| color(208) { m } }
918
+ super(*msgs, uplevel: 1)
919
+ end
826
920
 
827
- def perform
828
- mkdir_p 'lib'
829
- unless File.exist?('VERSION')
830
- File.open('VERSION', 'w') do |output|
831
- output.puts '0.0.0'
832
- end
833
- end
834
- unless File.exist?('Rakefile')
835
- File.open('Rakefile', 'w') do |output|
836
- output.puts <<~EOT
837
- # vim: set filetype=ruby et sw=2 ts=2:
921
+ # The fail method formats and displays failure messages using red colored
922
+ # output.
923
+ #
924
+ # @param args [Array] the array of arguments to be formatted and passed to super
925
+ def fail(*args)
926
+ args.map! do |a|
927
+ a.respond_to?(:to_str) ? color(196) { a.to_str } : a
928
+ end
929
+ super(*args)
930
+ end
931
+ def fail(*args)
932
+ args.map! do |a|
933
+ a.respond_to?(:to_str) ? color(196) { a.to_str } : a
934
+ end
935
+ super(*args)
936
+ end
838
937
 
839
- require 'gem_hadar'
938
+ # The git_remotes method retrieves the list of remote repositories configured
939
+ # for the current Git project.
940
+ #
941
+ # It first attempts to read the remotes from the ENV['GIT_REMOTE']
942
+ # environment variable, splitting it by whitespace. If this is not available,
943
+ # it falls back to querying the local Git repository using `git remote`.
944
+ #
945
+ # @return [ Array<String> ] an array of remote names
946
+ def git_remotes
947
+ remotes = ENV['GIT_REMOTE'].full?(:split, /\s+/)
948
+ remotes or remotes = `git remote`.lines.map(&:chomp)
949
+ remotes
950
+ end
840
951
 
841
- GemHadar do
842
- #developing true
843
- #name 'TODO'
844
- module_type :class
845
- #author 'TODO'
846
- #email 'todo@example.com'
847
- #homepage "https://github.com/TODO/NAME"
848
- #summary 'TODO'
849
- description 'TODO'
850
- test_dir 'spec'
851
- ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.AppleDouble', '.bundle', '.yardoc', 'tags'
852
- readme 'README.md'
952
+ # The ask? method prompts the user with a message and reads their input It
953
+ # returns a MatchData object if the input matches the provided pattern.
954
+ #
955
+ # @param prompt [ String ] the message to display to the user
956
+ # @param pattern [ Regexp ] the regular expression to match against the input
957
+ #
958
+ # @return [ MatchData, nil ] the result of the pattern match or nil if no match
959
+ def ask?(prompt, pattern, default: nil)
960
+ if prompt.include?('%{default}')
961
+ if default.present?
962
+ prompt = prompt % { default: ", default is #{default.inspect}" }
963
+ else
964
+ prompt = prompt % { default: '' }
965
+ end
966
+ end
967
+ STDOUT.print prompt
968
+ answer = STDIN.gets.chomp
969
+ default.present? && answer.blank? and answer = default
970
+ if answer =~ pattern
971
+ $~
972
+ end
973
+ end
853
974
 
854
- #executables << 'bin/TODO'
975
+ # The gem_files method returns an array of files that are included in the gem
976
+ # package.
977
+ #
978
+ # It calculates this by subtracting the files listed in package_ignore_files
979
+ # from the list of all files.
980
+ #
981
+ # @return [ Array<String> ] the list of files to include in the gem package
982
+ def gem_files
983
+ (files.to_a - package_ignore_files.to_a)
984
+ end
855
985
 
856
- #dependency 'TODO', '~>1.2.3'
986
+ # The versions method retrieves and processes the list of git tags that match
987
+ # semantic versioning patterns.
988
+ #
989
+ # It executes `git tag` to get all available tags, filters them using a
990
+ # regular expression to identify valid version strings, removes any 'v'
991
+ # prefix from each version string, trims whitespace, and sorts the resulting
992
+ # array based on semantic versioning order.
993
+ #
994
+ # @return [ Array<String> ] an array of version strings sorted in ascending
995
+ # order according to semantic versioning rules.
996
+ def versions
997
+ @versions ||= `git tag`.lines.grep(/^v?\d+\.\d+\.\d+$/).map(&:chomp).map {
998
+ _1.sub(/\Av/, '')
999
+ }.sort_by(&:version)
1000
+ end
857
1001
 
858
- #licenses << 'TODO
859
- end
860
- EOT
1002
+ # The version_identifier method prepends a 'v' prefix to the given version
1003
+ # string.
1004
+ #
1005
+ # @param version [String] the version string to modify
1006
+ # @return [String] the modified version string with a 'v' prefix
1007
+ def version_identifier(version)
1008
+ version.dup.prepend ?v
1009
+ end
1010
+
1011
+ # The github_remote_url method retrieves and parses the GitHub remote URL
1012
+ # from the local Git configuration.
1013
+ #
1014
+ # It executes `git remote -v` to get all remote configurations, extracts the
1015
+ # push URLs, processes them to construct valid URIs, and returns the first
1016
+ # URI pointing to GitHub.com.
1017
+ #
1018
+ # @return [URI, nil] The parsed GitHub remote URI or nil if not found.
1019
+ def github_remote_url
1020
+ if remotes = `git remote -v`
1021
+ remotes_urls = remotes.scan(/^(\S+)\s+(\S+)\s+\(push\)/)
1022
+ remotes_uris = remotes_urls.map do |name, url|
1023
+ if %r(\A(?<scheme>[^@]+)@(?<hostname>[A-Za-z0-9.]+):(?:\d*)(?<path>.*)) =~ url
1024
+ path = ?/ + path unless path.start_with? ?/
1025
+ url = 'ssh://%s@%s%s' % [ scheme, hostname, path ] # approximate correct URIs
861
1026
  end
1027
+ URI.parse(url)
862
1028
  end
1029
+ remotes_uris.find { |uri| uri.hostname == 'github.com' }
863
1030
  end
864
1031
  end
1032
+
1033
+ class << self
1034
+ # The start_simplecov method initializes SimpleCov and configures it to
1035
+ # ignore coverage data from the directory containing the caller. This can be
1036
+ # called from a test or spec helper.
1037
+ def start_simplecov
1038
+ defined? SimpleCov or return
1039
+ filter = "#{File.basename(File.dirname(caller.first))}/"
1040
+ SimpleCov.start do
1041
+ add_filter filter
1042
+ end
1043
+ end
1044
+ end
1045
+ end
1046
+
1047
+ def GemHadar(&block)
1048
+ GemHadar.new(&block).create_all_tasks
865
1049
  end
866
1050
 
867
1051
  def template(pathname, &block)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gem_hadar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.0
4
+ version: 1.21.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: gem_hadar
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1.19'
18
+ version: '1.21'
19
19
  type: :development
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '1.19'
25
+ version: '1.21'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: tins
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: ollama-ruby
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: rake
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -88,6 +102,9 @@ extensions: []
88
102
  extra_rdoc_files:
89
103
  - README.md
90
104
  - lib/gem_hadar.rb
105
+ - lib/gem_hadar/github.rb
106
+ - lib/gem_hadar/setup.rb
107
+ - lib/gem_hadar/template_compiler.rb
91
108
  - lib/gem_hadar/version.rb
92
109
  files:
93
110
  - ".gitignore"
@@ -99,6 +116,9 @@ files:
99
116
  - bin/gem_hadar
100
117
  - gem_hadar.gemspec
101
118
  - lib/gem_hadar.rb
119
+ - lib/gem_hadar/github.rb
120
+ - lib/gem_hadar/setup.rb
121
+ - lib/gem_hadar/template_compiler.rb
102
122
  - lib/gem_hadar/version.rb
103
123
  homepage: https://github.com/flori/gem_hadar
104
124
  licenses:
@@ -122,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
142
  - !ruby/object:Gem::Version
123
143
  version: '0'
124
144
  requirements: []
125
- rubygems_version: 3.6.2
145
+ rubygems_version: 3.6.9
126
146
  specification_version: 4
127
147
  summary: Library for the development of Ruby Gems
128
148
  test_files: []