ikuzo 0.1.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 +28 -12
- data/ikuzo.gemspec +2 -2
- data/lib/ikuzo/cli.rb +29 -1
- data/lib/ikuzo/pricing.rb +185 -0
- data/lib/ikuzo/version.rb +1 -1
- metadata +7 -5
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,7 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ikuzo 行くぞ (いくぞ)
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/ikuzo)
|
|
4
|
+
|
|
5
|
+
[](https://www.buymeacoffee.com/ojisanchamchi)
|
|
2
6
|
|
|
3
7
|
Ikuzo is a Ruby gem that generates short, motivational commit messages for developers. Use it as a CLI tool or as a library inside your scripts when you need an instant morale boost.
|
|
4
8
|
|
|
9
|
+
I built it after one too many blank stares at my terminal, trying to squeeze yet another branch name into a Conventional Commit subject. Life is short, take time to code, so I'd rather spend the focus on shipping than negotiating with commit lint rules. With Ikuzo, I can fire off a compliant, upbeat message in seconds and get back to building.
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -21,7 +27,7 @@ bundle install
|
|
|
21
27
|
Outputs vary because messages are chosen at random. Ikuzo commits automatically; add `--no-commit` if you only want to print the message.
|
|
22
28
|
|
|
23
29
|
Commit with a random message (default: feat):
|
|
24
|
-
|
|
30
|
+
```
|
|
25
31
|
$ ikuzo
|
|
26
32
|
git commit -m "feat: main linted the vibes not the code"
|
|
27
33
|
[main abc1234] feat: main linted the vibes not the code
|
|
@@ -29,13 +35,13 @@ git commit -m "feat: main linted the vibes not the code"
|
|
|
29
35
|
```
|
|
30
36
|
|
|
31
37
|
Print a random message from a specific category without committing:
|
|
32
|
-
|
|
38
|
+
```
|
|
33
39
|
$ ikuzo funny --no-commit
|
|
34
40
|
This commit was pair-programmed with caffeine.
|
|
35
41
|
```
|
|
36
42
|
|
|
37
43
|
Commit with a random message explicitly (also default behavior):
|
|
38
|
-
|
|
44
|
+
```
|
|
39
45
|
$ ikuzo commit
|
|
40
46
|
git commit -m "It compiled on my machine, scout's honor."
|
|
41
47
|
[main abc1234] It compiled on my machine, scout's honor.
|
|
@@ -43,13 +49,13 @@ git commit -m "It compiled on my machine, scout's honor."
|
|
|
43
49
|
```
|
|
44
50
|
|
|
45
51
|
Skip committing and just print the message:
|
|
46
|
-
|
|
52
|
+
```
|
|
47
53
|
$ ikuzo --no-commit
|
|
48
54
|
feat: main linted the vibes not the code
|
|
49
55
|
```
|
|
50
56
|
|
|
51
57
|
Specify a category explicitly and commit:
|
|
52
|
-
|
|
58
|
+
```
|
|
53
59
|
$ ikuzo --category dev
|
|
54
60
|
git commit -m "Logs cleaned, metrics gleam."
|
|
55
61
|
[main ghi9012] Logs cleaned, metrics gleam.
|
|
@@ -57,23 +63,33 @@ git commit -m "Logs cleaned, metrics gleam."
|
|
|
57
63
|
```
|
|
58
64
|
|
|
59
65
|
Generate a Conventional Commit message from the current branch (omit `--no-commit` to auto commit):
|
|
60
|
-
|
|
66
|
+
```
|
|
61
67
|
$ ikuzo feat --no-commit
|
|
62
68
|
feat: main no rubber duck was harmed in this fix
|
|
63
69
|
```
|
|
64
70
|
|
|
65
|
-
|
|
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)
|
|
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.
|
|
66
80
|
|
|
81
|
+
Pick any Conventional Commit type that you need:
|
|
82
|
+
```
|
|
67
83
|
$ ikuzo fix
|
|
68
84
|
git commit -m "fix: main stack trace more like snack trace"
|
|
69
85
|
[main jkl3456] fix: main stack trace more like snack trace
|
|
70
86
|
1 file changed, 1 insertion(+)
|
|
71
87
|
```
|
|
72
88
|
|
|
73
|
-
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.
|
|
74
90
|
|
|
75
91
|
List available categories:
|
|
76
|
-
|
|
92
|
+
```
|
|
77
93
|
$ ikuzo --list-categories
|
|
78
94
|
Available categories:
|
|
79
95
|
- general
|
|
@@ -94,9 +110,9 @@ Available categories:
|
|
|
94
110
|
```
|
|
95
111
|
|
|
96
112
|
Show the installed version:
|
|
97
|
-
|
|
113
|
+
```
|
|
98
114
|
$ ikuzo --version
|
|
99
|
-
|
|
115
|
+
1.1.0
|
|
100
116
|
```
|
|
101
117
|
|
|
102
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/ikuzo.gemspec
CHANGED
|
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
|
|
|
6
6
|
spec.authors = ["Dang Quang Minh"]
|
|
7
7
|
spec.email = ["ojisanchamchi@gmail.com"]
|
|
8
8
|
|
|
9
|
-
spec.summary = "Generate
|
|
10
|
-
spec.description = "Ikuzo delivers short, humorous, and motivational commit messages
|
|
9
|
+
spec.summary = "Generate convention-ready commit messages without overthinking them."
|
|
10
|
+
spec.description = "Ikuzo delivers short, humorous, and motivational commit messages so you can satisfy Conventional Commit lint rules and get back to coding—life is short, take time to code—whether you call it from the CLI or as a library."
|
|
11
11
|
spec.homepage = "https://github.com/ojisanchamchi/ruby_ikuzo#readme"
|
|
12
12
|
spec.license = "MIT"
|
|
13
13
|
|
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:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dang Quang Minh
|
|
@@ -37,8 +37,9 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '13.0'
|
|
40
|
-
description: Ikuzo delivers short, humorous, and motivational commit messages
|
|
41
|
-
|
|
40
|
+
description: Ikuzo delivers short, humorous, and motivational commit messages so you
|
|
41
|
+
can satisfy Conventional Commit lint rules and get back to coding—life is short,
|
|
42
|
+
take time to code—whether you call it from the CLI or as a library.
|
|
42
43
|
email:
|
|
43
44
|
- ojisanchamchi@gmail.com
|
|
44
45
|
executables:
|
|
@@ -53,6 +54,7 @@ files:
|
|
|
53
54
|
- lib/ikuzo.rb
|
|
54
55
|
- lib/ikuzo/cli.rb
|
|
55
56
|
- lib/ikuzo/messages.rb
|
|
57
|
+
- lib/ikuzo/pricing.rb
|
|
56
58
|
- lib/ikuzo/version.rb
|
|
57
59
|
homepage: https://github.com/ojisanchamchi/ruby_ikuzo#readme
|
|
58
60
|
licenses:
|
|
@@ -75,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
75
77
|
- !ruby/object:Gem::Version
|
|
76
78
|
version: '0'
|
|
77
79
|
requirements: []
|
|
78
|
-
rubygems_version: 3.6.
|
|
80
|
+
rubygems_version: 3.6.7
|
|
79
81
|
specification_version: 4
|
|
80
|
-
summary: Generate
|
|
82
|
+
summary: Generate convention-ready commit messages without overthinking them.
|
|
81
83
|
test_files: []
|