ikuzo 1.0.0 → 1.1.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 +22 -12
- data/lib/ikuzo/cli.rb +29 -1
- data/lib/ikuzo/pricing.rb +185 -0
- data/lib/ikuzo/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '099190e70536dbf6c718f5b10e3bbfe5e6422a8791357432b44f457c38d2eea6'
|
|
4
|
+
data.tar.gz: 13ae73024dbb2b86a6693731a9a767c284cb341a5f8a63533a96daf1825b7c83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76e94226bf2a67848cbb1293f6b5c1f50c501af0f78f0372fac51b4439bf8d632309c21e054c0228dab87b7b862cc7c1f2098a0970e9c015ab7935ce1f51deb1
|
|
7
|
+
data.tar.gz: 4dc503e5b4c8788a2fde76772eb845e6d9d77ac426a94bd099563ced4ec3687147fae36e91aa501e8d4de731249ca0f60cb9c43930b47724e31a29c0deef65a7
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ikuzo 行くぞ (いくぞ)
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/ikuzo)
|
|
4
4
|
|
|
@@ -27,7 +27,7 @@ bundle install
|
|
|
27
27
|
Outputs vary because messages are chosen at random. Ikuzo commits automatically; add `--no-commit` if you only want to print the message.
|
|
28
28
|
|
|
29
29
|
Commit with a random message (default: feat):
|
|
30
|
-
|
|
30
|
+
```
|
|
31
31
|
$ ikuzo
|
|
32
32
|
git commit -m "feat: main linted the vibes not the code"
|
|
33
33
|
[main abc1234] feat: main linted the vibes not the code
|
|
@@ -35,13 +35,13 @@ git commit -m "feat: main linted the vibes not the code"
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Print a random message from a specific category without committing:
|
|
38
|
-
|
|
38
|
+
```
|
|
39
39
|
$ ikuzo funny --no-commit
|
|
40
40
|
This commit was pair-programmed with caffeine.
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Commit with a random message explicitly (also default behavior):
|
|
44
|
-
|
|
44
|
+
```
|
|
45
45
|
$ ikuzo commit
|
|
46
46
|
git commit -m "It compiled on my machine, scout's honor."
|
|
47
47
|
[main abc1234] It compiled on my machine, scout's honor.
|
|
@@ -49,13 +49,13 @@ git commit -m "It compiled on my machine, scout's honor."
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Skip committing and just print the message:
|
|
52
|
-
|
|
52
|
+
```
|
|
53
53
|
$ ikuzo --no-commit
|
|
54
54
|
feat: main linted the vibes not the code
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Specify a category explicitly and commit:
|
|
58
|
-
|
|
58
|
+
```
|
|
59
59
|
$ ikuzo --category dev
|
|
60
60
|
git commit -m "Logs cleaned, metrics gleam."
|
|
61
61
|
[main ghi9012] Logs cleaned, metrics gleam.
|
|
@@ -63,23 +63,33 @@ git commit -m "Logs cleaned, metrics gleam."
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
Generate a Conventional Commit message from the current branch (omit `--no-commit` to auto commit):
|
|
66
|
-
|
|
66
|
+
```
|
|
67
67
|
$ ikuzo feat --no-commit
|
|
68
68
|
feat: main no rubber duck was harmed in this fix
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Append BTC or XAU pricing to the generated message (use `xau` or `gold`):
|
|
72
|
+
```
|
|
73
|
+
$ ikuzo --price=btc --no-commit
|
|
74
|
+
fix: stack trace more like snack trace (BTC: $64,123.45)
|
|
72
75
|
|
|
76
|
+
$ ikuzo --price=xau --no-commit
|
|
77
|
+
chore: merge dreams into main (XAU: $2,045.67)
|
|
78
|
+
```
|
|
79
|
+
> Pricing data comes from CoinGecko (BTC) and GoldPrice.org (XAU). If the lookup fails, Ikuzo keeps the original message without the price suffix. When Ruby cannot complete the HTTPS request, Ikuzo retries with `curl` if it is available. TLS certificate checks are disabled by default for CoinGecko requests; set `IKUZO_INSECURE_SSL=0` if you want to enforce verification instead.
|
|
80
|
+
|
|
81
|
+
Pick any Conventional Commit type that you need:
|
|
82
|
+
```
|
|
73
83
|
$ ikuzo fix
|
|
74
84
|
git commit -m "fix: main stack trace more like snack trace"
|
|
75
85
|
[main jkl3456] fix: main stack trace more like snack trace
|
|
76
86
|
1 file changed, 1 insertion(+)
|
|
77
87
|
```
|
|
78
88
|
|
|
79
|
-
Supported types align with the Conventional Commits 1.
|
|
89
|
+
Supported types align with the Conventional Commits 1.1.0 spec: build, ci, chore, docs, feat, fix, perf, refactor, revert, style, and test.
|
|
80
90
|
|
|
81
91
|
List available categories:
|
|
82
|
-
|
|
92
|
+
```
|
|
83
93
|
$ ikuzo --list-categories
|
|
84
94
|
Available categories:
|
|
85
95
|
- general
|
|
@@ -100,9 +110,9 @@ Available categories:
|
|
|
100
110
|
```
|
|
101
111
|
|
|
102
112
|
Show the installed version:
|
|
103
|
-
|
|
113
|
+
```
|
|
104
114
|
$ ikuzo --version
|
|
105
|
-
1.
|
|
115
|
+
1.1.0
|
|
106
116
|
```
|
|
107
117
|
|
|
108
118
|
Each Conventional Commit category builds a `<type>: ...` message from your current Git branch, appends a cleaned-up motivational quip, and falls back to a default subject if the branch cannot be detected.
|
data/lib/ikuzo/cli.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
|
+
require "ikuzo/pricing"
|
|
4
5
|
|
|
5
6
|
module Ikuzo
|
|
6
7
|
# CLI entry point for generating random commit messages.
|
|
@@ -21,6 +22,7 @@ module Ikuzo
|
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
message = Messages.random(options[:category])
|
|
25
|
+
message = append_price(message, options[:price]) if options[:price]
|
|
24
26
|
@stdout.puts(message)
|
|
25
27
|
|
|
26
28
|
return exit_success unless options[:commit]
|
|
@@ -38,7 +40,8 @@ module Ikuzo
|
|
|
38
40
|
options = {
|
|
39
41
|
commit: true,
|
|
40
42
|
list_categories: false,
|
|
41
|
-
category: nil
|
|
43
|
+
category: nil,
|
|
44
|
+
price: nil
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
categories_text = Messages.categories.join(", ")
|
|
@@ -58,6 +61,10 @@ module Ikuzo
|
|
|
58
61
|
options[:list_categories] = true
|
|
59
62
|
end
|
|
60
63
|
|
|
64
|
+
option_parser.on("-pASSET", "--price=ASSET", "Append pricing for btc or xau (gold) to the message") do |asset|
|
|
65
|
+
options[:price] = normalize_price_asset(asset)
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
option_parser.on("-v", "--version", "Print version") do
|
|
62
69
|
@stdout.puts(Ikuzo::VERSION)
|
|
63
70
|
exit_success
|
|
@@ -86,6 +93,27 @@ module Ikuzo
|
|
|
86
93
|
end
|
|
87
94
|
end
|
|
88
95
|
|
|
96
|
+
def normalize_price_asset(asset)
|
|
97
|
+
normalized = asset.to_s.strip.downcase
|
|
98
|
+
return :btc if %w[btc bitcoin].include?(normalized)
|
|
99
|
+
return :xau if normalized == "xau" || normalized == "gold"
|
|
100
|
+
|
|
101
|
+
raise OptionParser::InvalidArgument, "Unknown price asset '#{asset}'. Supported values: btc, xau."
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def append_price(message, asset)
|
|
105
|
+
suffix = Pricing.suffix_for(asset)
|
|
106
|
+
unless suffix
|
|
107
|
+
@stderr.puts("Unable to fetch pricing for #{asset.to_s.upcase}; continuing without price.")
|
|
108
|
+
return message
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
"#{message} #{suffix}"
|
|
112
|
+
rescue StandardError => e
|
|
113
|
+
@stderr.puts("Failed to append #{asset.to_s.upcase} price: #{e.message}")
|
|
114
|
+
message
|
|
115
|
+
end
|
|
116
|
+
|
|
89
117
|
def apply_positional_arguments(options)
|
|
90
118
|
@argv.each do |arg|
|
|
91
119
|
normalized = arg.to_s.strip.downcase
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "openssl"
|
|
7
|
+
require "open3"
|
|
8
|
+
|
|
9
|
+
module Ikuzo
|
|
10
|
+
# Fetches cryptocurrency and metal pricing information for use in commit messages.
|
|
11
|
+
class Pricing
|
|
12
|
+
DEFAULT_CURRENCY = "USD"
|
|
13
|
+
COINGECKO_ENDPOINT = "https://api.coingecko.com/api/v3/simple/price"
|
|
14
|
+
|
|
15
|
+
SUPPORTED_ASSETS = {
|
|
16
|
+
btc: { id: "bitcoin", label: "BTC", provider: :coingecko },
|
|
17
|
+
xau: { symbol: "XAU", label: "XAU", provider: :metal }
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
def self.suffix_for(asset, currency: DEFAULT_CURRENCY)
|
|
21
|
+
asset = asset.to_sym
|
|
22
|
+
return unless SUPPORTED_ASSETS.key?(asset)
|
|
23
|
+
|
|
24
|
+
price = fetch_price(asset, currency: currency)
|
|
25
|
+
return unless price
|
|
26
|
+
|
|
27
|
+
"#{suffix_prefix(asset)} #{format_amount(price, currency)}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.fetch_price(asset, currency: DEFAULT_CURRENCY)
|
|
31
|
+
info = SUPPORTED_ASSETS.fetch(asset)
|
|
32
|
+
case info.fetch(:provider)
|
|
33
|
+
when :coingecko
|
|
34
|
+
fetch_coingecko_price(info.fetch(:id), currency)
|
|
35
|
+
when :metal
|
|
36
|
+
fetch_metal_price(currency)
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
private_class_method :fetch_price
|
|
42
|
+
|
|
43
|
+
def self.fetch_coingecko_price(asset_id, currency)
|
|
44
|
+
response = request_coingecko_price(asset_id, currency: currency)
|
|
45
|
+
response = request_coingecko_price_via_curl(asset_id, currency: currency) if response.nil? && curl_available?
|
|
46
|
+
return unless response
|
|
47
|
+
|
|
48
|
+
asset_data = response[asset_id] || response[asset_id.to_sym]
|
|
49
|
+
return unless asset_data.is_a?(Hash)
|
|
50
|
+
|
|
51
|
+
lookup_key = currency.downcase
|
|
52
|
+
value = asset_data[lookup_key]
|
|
53
|
+
return unless value
|
|
54
|
+
Float(value)
|
|
55
|
+
rescue ArgumentError
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
private_class_method :fetch_coingecko_price
|
|
59
|
+
|
|
60
|
+
def self.fetch_metal_price(currency)
|
|
61
|
+
return unless currency.upcase == DEFAULT_CURRENCY
|
|
62
|
+
|
|
63
|
+
uri = URI("https://data-asg.goldprice.org/dbXRates/#{currency.upcase}")
|
|
64
|
+
response = http_get(uri)
|
|
65
|
+
return unless response.is_a?(Net::HTTPSuccess)
|
|
66
|
+
|
|
67
|
+
data = JSON.parse(response.body)
|
|
68
|
+
items = data["items"]
|
|
69
|
+
return unless items.is_a?(Array) && !items.empty?
|
|
70
|
+
|
|
71
|
+
value = items.first["xauPrice"]
|
|
72
|
+
Float(value)
|
|
73
|
+
rescue StandardError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
private_class_method :fetch_metal_price
|
|
77
|
+
|
|
78
|
+
def self.request_coingecko_price(asset_id, currency: DEFAULT_CURRENCY)
|
|
79
|
+
uri = URI(COINGECKO_ENDPOINT)
|
|
80
|
+
uri.query = URI.encode_www_form(ids: asset_id, vs_currencies: currency.downcase)
|
|
81
|
+
|
|
82
|
+
response = http_get(uri)
|
|
83
|
+
return unless response
|
|
84
|
+
|
|
85
|
+
return unless response.is_a?(Net::HTTPSuccess)
|
|
86
|
+
|
|
87
|
+
data = JSON.parse(response.body)
|
|
88
|
+
data
|
|
89
|
+
rescue StandardError
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
private_class_method :request_coingecko_price
|
|
93
|
+
|
|
94
|
+
def self.request_coingecko_price_via_curl(asset_id, currency: DEFAULT_CURRENCY)
|
|
95
|
+
uri = URI(COINGECKO_ENDPOINT)
|
|
96
|
+
uri.query = URI.encode_www_form(ids: asset_id, vs_currencies: currency.downcase)
|
|
97
|
+
|
|
98
|
+
stdout, status = Open3.capture2("curl", "-sS", uri.to_s)
|
|
99
|
+
return nil unless status.success?
|
|
100
|
+
|
|
101
|
+
JSON.parse(stdout)
|
|
102
|
+
rescue StandardError
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
private_class_method :request_coingecko_price_via_curl
|
|
106
|
+
|
|
107
|
+
def self.http_get(uri)
|
|
108
|
+
insecure = insecure_ssl?
|
|
109
|
+
response = perform_http_get(uri, verify: !insecure)
|
|
110
|
+
response
|
|
111
|
+
rescue OpenSSL::SSL::SSLError
|
|
112
|
+
unless insecure
|
|
113
|
+
return perform_http_get(uri, verify: false)
|
|
114
|
+
end
|
|
115
|
+
nil
|
|
116
|
+
rescue StandardError
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
private_class_method :http_get
|
|
120
|
+
|
|
121
|
+
def self.perform_http_get(uri, verify:)
|
|
122
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
123
|
+
http.use_ssl = uri.scheme == "https"
|
|
124
|
+
http.open_timeout = 5
|
|
125
|
+
http.read_timeout = 5
|
|
126
|
+
if http.use_ssl?
|
|
127
|
+
if verify
|
|
128
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
129
|
+
http.cert_store = default_cert_store
|
|
130
|
+
else
|
|
131
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
request = Net::HTTP::Get.new(uri)
|
|
136
|
+
request["User-Agent"] = USER_AGENT
|
|
137
|
+
request["Accept"] = "application/json"
|
|
138
|
+
|
|
139
|
+
http.request(request)
|
|
140
|
+
end
|
|
141
|
+
private_class_method :perform_http_get
|
|
142
|
+
|
|
143
|
+
USER_AGENT = "Ikuzo CLI/1.0"
|
|
144
|
+
|
|
145
|
+
def self.insecure_ssl?
|
|
146
|
+
ENV.fetch("IKUZO_INSECURE_SSL", "1") != "0"
|
|
147
|
+
end
|
|
148
|
+
private_class_method :insecure_ssl?
|
|
149
|
+
|
|
150
|
+
def self.default_cert_store
|
|
151
|
+
@default_cert_store ||= OpenSSL::X509::Store.new.tap do |store|
|
|
152
|
+
store.set_default_paths
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
private_class_method :default_cert_store
|
|
156
|
+
|
|
157
|
+
def self.curl_available?
|
|
158
|
+
@curl_available ||= system("command -v curl > /dev/null 2>&1")
|
|
159
|
+
end
|
|
160
|
+
private_class_method :curl_available?
|
|
161
|
+
|
|
162
|
+
def self.suffix_prefix(asset)
|
|
163
|
+
"(#{SUPPORTED_ASSETS.fetch(asset).fetch(:label)}:"
|
|
164
|
+
end
|
|
165
|
+
private_class_method :suffix_prefix
|
|
166
|
+
|
|
167
|
+
def self.format_amount(value, currency)
|
|
168
|
+
formatted_number = delimited(format("%.2f", value))
|
|
169
|
+
"#{currency_symbol(currency)}#{formatted_number})"
|
|
170
|
+
end
|
|
171
|
+
private_class_method :format_amount
|
|
172
|
+
|
|
173
|
+
def self.delimited(value)
|
|
174
|
+
integer, fraction = value.split(".")
|
|
175
|
+
integer_with_commas = integer.gsub(/(\d)(?=(\d{3})+(?!\d))/, "\\1,")
|
|
176
|
+
[integer_with_commas, fraction].compact.join(".")
|
|
177
|
+
end
|
|
178
|
+
private_class_method :delimited
|
|
179
|
+
|
|
180
|
+
def self.currency_symbol(currency)
|
|
181
|
+
currency.upcase == "USD" ? "$" : "#{currency.upcase} "
|
|
182
|
+
end
|
|
183
|
+
private_class_method :currency_symbol
|
|
184
|
+
end
|
|
185
|
+
end
|
data/lib/ikuzo/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ikuzo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dang Quang Minh
|
|
@@ -54,6 +54,7 @@ files:
|
|
|
54
54
|
- lib/ikuzo.rb
|
|
55
55
|
- lib/ikuzo/cli.rb
|
|
56
56
|
- lib/ikuzo/messages.rb
|
|
57
|
+
- lib/ikuzo/pricing.rb
|
|
57
58
|
- lib/ikuzo/version.rb
|
|
58
59
|
homepage: https://github.com/ojisanchamchi/ruby_ikuzo#readme
|
|
59
60
|
licenses:
|
|
@@ -76,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
76
77
|
- !ruby/object:Gem::Version
|
|
77
78
|
version: '0'
|
|
78
79
|
requirements: []
|
|
79
|
-
rubygems_version: 3.6.
|
|
80
|
+
rubygems_version: 3.6.7
|
|
80
81
|
specification_version: 4
|
|
81
82
|
summary: Generate convention-ready commit messages without overthinking them.
|
|
82
83
|
test_files: []
|