workout_menu 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: affc512b58e73f670c740957dfa8def3cc53e6e462ec7aa8a32fec11c782130f
4
+ data.tar.gz: 4772495becc710e6ca3508bbfa670f2631cf592f4b17d5468bd97aa1885801f2
5
+ SHA512:
6
+ metadata.gz: 2f8b3c18ec96356a132d73736eb50c193ed78678867f541698ed8ec74532089db352e53c65543e564a04adba5b7285653c7630e42d67fa6bf5d1f1563fc9a02e
7
+ data.tar.gz: 4ac9922e776437792a48d72ced012eb10380db1b8387c232d74f1d8650bc69d7f4911f55d59e67abe54a5f6a49ba3fd67a9546ae1c22da349d44335520ddd255
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-11-03
4
+
5
+ - Initial release
6
+ - `workout_menu` コマンドを追加。部位 (`--part`) と場所 (`--place`) を指定して、トレーニングメニューを提案する
7
+ - 胸、背中、脚、肩、腕、腹筋の6部位、自宅とジムの2か所に対応
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 T-REX1970
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # WorkoutMenu
2
+
3
+ 「今日、何を鍛えよう?」と迷う時間をなくす、筋トレメニュー提案コマンドです。
4
+ 鍛えたい部位と場所を指定すると、ランダムに種目を選んで、セット数と回数つきで表示します。
5
+
6
+ ```console
7
+ $ workout_menu --part chest --place home
8
+ 今日のメニュー: 胸 / 自宅
9
+ 1. ワイドプッシュアップ 3セット × 12回
10
+ 2. 腕立て伏せ 3セット × 15回
11
+ 3. デクラインプッシュアップ 3セット × 12回
12
+ 4. インクラインプッシュアップ 3セット × 15回
13
+ ```
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ gem install workout_menu
19
+ ```
20
+
21
+ Bundler を使う場合は、Gemfile に次の行を追加します。
22
+
23
+ ```ruby
24
+ gem "workout_menu"
25
+ ```
26
+
27
+ Ruby 3.2 以上が必要です。
28
+
29
+ ## Usage
30
+
31
+ ```console
32
+ $ workout_menu --part PART [options]
33
+ ```
34
+
35
+ | オプション | 説明 | 既定値 |
36
+ |---|---|---|
37
+ | `-p`, `--part PART` | 鍛える部位(**必須**) | - |
38
+ | `-l`, `--place PLACE` | 場所 | `home` |
39
+ | `-n`, `--count N` | 種目数 | `4` |
40
+ | `-s`, `--seed N` | 乱数のシード。同じ値なら同じメニューになる | ランダム |
41
+ | `-v`, `--version` | バージョンを表示 | - |
42
+ | `-h`, `--help` | ヘルプを表示 | - |
43
+
44
+ ### 部位 (`--part`)
45
+
46
+ | 値 | 部位 |
47
+ |---|---|
48
+ | `chest` | 胸 |
49
+ | `back` | 背中 |
50
+ | `legs` | 脚 |
51
+ | `shoulders` | 肩 |
52
+ | `arms` | 腕 |
53
+ | `abs` | 腹筋 |
54
+
55
+ ### 場所 (`--place`)
56
+
57
+ | 値 | 内容 |
58
+ |---|---|
59
+ | `home` | 自宅。器具を使わない種目だけを提案する |
60
+ | `gym` | ジム。マシンやバーベルを使う種目も提案する |
61
+
62
+ ### 例
63
+
64
+ ```console
65
+ # ジムで脚を鍛える(種目数は 5)
66
+ $ workout_menu -p legs -l gym -n 5
67
+
68
+ # 同じシードなら、いつも同じメニューになる
69
+ $ workout_menu -p back -s 42
70
+ ```
71
+
72
+ 時間で行う種目(プランクなど)は、回数ではなく秒数で表示されます。
73
+
74
+ ```console
75
+ $ workout_menu -p abs -l gym -n 3 -s 2
76
+ 今日のメニュー: 腹筋 / ジム
77
+ 1. ケーブルクランチ 3セット × 15回
78
+ 2. ハンギングレッグレイズ 3セット × 10回
79
+ 3. マウンテンクライマー 3セット × 30秒
80
+ ```
81
+
82
+ ### エラー
83
+
84
+ 指定が正しくないときは、メッセージを標準エラー出力に出して、終了ステータス 1 で終了します。
85
+
86
+ ```console
87
+ $ workout_menu
88
+ workout_menu: --part is required (chest, back, legs, shoulders, arms, abs)
89
+ Try 'workout_menu --help' for usage.
90
+ ```
91
+
92
+ ### Ruby から使う
93
+
94
+ ```ruby
95
+ require "workout_menu"
96
+
97
+ menu = WorkoutMenu::Menu.new
98
+ menu.suggest(part: :chest, place: :home, count: 3).each do |exercise|
99
+ puts "#{exercise.name} #{exercise.volume}"
100
+ end
101
+ ```
102
+
103
+ ## 種目を追加・変更するには
104
+
105
+ 種目は [`lib/workout_menu/exercises.yml`](lib/workout_menu/exercises.yml) に定義されています。
106
+ 次の形式で追記すると、提案の対象になります。
107
+
108
+ ```yaml
109
+ - { name: 腕立て伏せ, part: chest, places: [home, gym], sets: 3, reps: 15 }
110
+ ```
111
+
112
+ 時間で行う種目は、`unit: 秒` を加えます。
113
+
114
+ ## Development
115
+
116
+ リポジトリを clone したあと、`bin/setup` で依存をインストールします。
117
+ `bundle exec rake` で、テストと RuboCop を実行します。`bin/console` で、対話的に試せます。
118
+
119
+ ```bash
120
+ bin/setup
121
+ bundle exec rake
122
+ bundle exec exe/workout_menu --part chest
123
+ ```
124
+
125
+ このgemをローカルにインストールするには `bundle exec rake install` を実行します。
126
+ 新しいバージョンをリリースするには、`lib/workout_menu/version.rb` のバージョンを更新して、`bundle exec rake release` を実行します。
127
+ gitタグの作成、コミットとタグの push、`.gem` ファイルの [rubygems.org](https://rubygems.org) への公開が行われます。
128
+
129
+ ## Contributing
130
+
131
+ バグ報告やプルリクエストは、GitHub の https://github.com/T-REX1996/workout_menu で歓迎します。
132
+
133
+ ## License
134
+
135
+ このgemは、[MIT License](https://opensource.org/licenses/MIT) の条件のもとでオープンソースとして公開されています。
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
data/exe/workout_menu ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "workout_menu"
5
+
6
+ exit WorkoutMenu::CLI.run(ARGV)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module WorkoutMenu
6
+ # 種目データの読み込みと絞り込みを担当する。
7
+ class Catalog
8
+ PARTS = {
9
+ chest: "胸",
10
+ back: "背中",
11
+ legs: "脚",
12
+ shoulders: "肩",
13
+ arms: "腕",
14
+ abs: "腹筋"
15
+ }.freeze
16
+
17
+ PLACES = {
18
+ home: "自宅",
19
+ gym: "ジム"
20
+ }.freeze
21
+
22
+ DEFAULT_PATH = File.expand_path("exercises.yml", __dir__)
23
+ DEFAULT_UNIT = "回"
24
+
25
+ def self.load(path = DEFAULT_PATH)
26
+ data = YAML.safe_load_file(path, symbolize_names: true)
27
+ new(data.fetch(:exercises).map { |attrs| build_exercise(attrs) })
28
+ end
29
+
30
+ def self.build_exercise(attrs)
31
+ Exercise.new(unit: DEFAULT_UNIT, **attrs, part: attrs[:part].to_sym, places: attrs[:places].map(&:to_sym))
32
+ end
33
+ private_class_method :build_exercise
34
+
35
+ def initialize(exercises)
36
+ @exercises = exercises.freeze
37
+ end
38
+
39
+ attr_reader :exercises
40
+
41
+ def select(part:, place:)
42
+ exercises.select { |e| e.part == part && e.places.include?(place) }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module WorkoutMenu
6
+ # コマンドライン入力を解釈して、メニューを出力する。
7
+ class CLI
8
+ DEFAULT_PLACE = "home"
9
+ DEFAULT_COUNT = 4
10
+
11
+ def self.run(argv, out: $stdout, err: $stderr)
12
+ new(out: out, err: err).run(argv)
13
+ end
14
+
15
+ def initialize(out:, err:)
16
+ @out = out
17
+ @err = err
18
+ end
19
+
20
+ # 終了ステータスを返す(成功: 0、失敗: 1)。
21
+ def run(argv)
22
+ options = { place: DEFAULT_PLACE, count: DEFAULT_COUNT }
23
+ parser = build_parser(options)
24
+ parser.parse(argv)
25
+ dispatch(options, parser)
26
+ 0
27
+ rescue OptionParser::ParseError, Error => e
28
+ @err.puts "workout_menu: #{e.message}"
29
+ @err.puts "Try 'workout_menu --help' for usage."
30
+ 1
31
+ end
32
+
33
+ private
34
+
35
+ def dispatch(options, parser)
36
+ if options[:help]
37
+ @out.puts parser.help
38
+ elsif options[:version]
39
+ @out.puts VERSION
40
+ else
41
+ print_menu(options)
42
+ end
43
+ end
44
+
45
+ def build_parser(options)
46
+ OptionParser.new do |parser|
47
+ parser.banner = "Usage: workout_menu --part PART [options]"
48
+ define_target_options(parser, options)
49
+ define_amount_options(parser, options)
50
+ define_info_options(parser, options)
51
+ end
52
+ end
53
+
54
+ def define_target_options(parser, options)
55
+ parts = Catalog::PARTS.keys
56
+ places = Catalog::PLACES.keys
57
+ parser.on("-p", "--part PART", parts.map(&:to_s), "鍛える部位 (#{parts.join(", ")})") { |v| options[:part] = v }
58
+ parser.on("-l", "--place PLACE", places.map(&:to_s), "場所 (#{places.join(", ")}) 既定: #{DEFAULT_PLACE}") do |v|
59
+ options[:place] = v
60
+ end
61
+ end
62
+
63
+ def define_amount_options(parser, options)
64
+ parser.on("-n", "--count N", Integer, "種目数 既定: #{DEFAULT_COUNT}") { |v| options[:count] = v }
65
+ parser.on("-s", "--seed N", Integer, "乱数のシード(同じ値なら同じメニュー)") { |v| options[:seed] = v }
66
+ end
67
+
68
+ def define_info_options(parser, options)
69
+ parser.on("-v", "--version", "バージョンを表示") { options[:version] = true }
70
+ parser.on("-h", "--help", "ヘルプを表示") { options[:help] = true }
71
+ end
72
+
73
+ def print_menu(options)
74
+ raise Error, "--part is required (#{Catalog::PARTS.keys.join(", ")})" unless options[:part]
75
+
76
+ part = options[:part].to_sym
77
+ place = options[:place].to_sym
78
+ menu = Menu.new(random: random_for(options[:seed]))
79
+ print_exercises(part, place, menu.suggest(part: part, place: place, count: options[:count]))
80
+ end
81
+
82
+ def random_for(seed)
83
+ seed ? Random.new(seed) : Random.new
84
+ end
85
+
86
+ def print_exercises(part, place, exercises)
87
+ @out.puts "今日のメニュー: #{Catalog::PARTS[part]} / #{Catalog::PLACES[place]}"
88
+ exercises.each.with_index(1) { |e, i| @out.puts "#{i}. #{e.name} #{e.volume}" }
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkoutMenu
4
+ # 1つのトレーニング種目。
5
+ Exercise = Data.define(:name, :part, :places, :sets, :reps, :unit) do
6
+ # 例: "3セット × 15回"
7
+ def volume
8
+ "#{sets}セット × #{reps}#{unit}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,65 @@
1
+ # 種目データ
2
+ # part: chest / back / legs / shoulders / arms / abs
3
+ # places: home(自宅・器具なし) / gym(ジム)
4
+ # unit: 省略すると「回」。時間で行う種目は「秒」を指定する
5
+ exercises:
6
+ # --- 胸 ---
7
+ - { name: 腕立て伏せ, part: chest, places: [home, gym], sets: 3, reps: 15 }
8
+ - { name: ワイドプッシュアップ, part: chest, places: [home, gym], sets: 3, reps: 12 }
9
+ - { name: デクラインプッシュアップ, part: chest, places: [home], sets: 3, reps: 12 }
10
+ - { name: インクラインプッシュアップ, part: chest, places: [home], sets: 3, reps: 15 }
11
+ - { name: ベンチプレス, part: chest, places: [gym], sets: 3, reps: 8 }
12
+ - { name: インクラインダンベルプレス, part: chest, places: [gym], sets: 3, reps: 10 }
13
+ - { name: ダンベルフライ, part: chest, places: [gym], sets: 3, reps: 12 }
14
+ - { name: チェストプレス, part: chest, places: [gym], sets: 3, reps: 10 }
15
+
16
+ # --- 背中 ---
17
+ - { name: バックエクステンション, part: back, places: [home, gym], sets: 3, reps: 15 }
18
+ - { name: スーパーマン, part: back, places: [home], sets: 3, reps: 15 }
19
+ - { name: リバーススノーエンジェル, part: back, places: [home], sets: 3, reps: 12 }
20
+ - { name: 懸垂, part: back, places: [gym], sets: 3, reps: 8 }
21
+ - { name: ラットプルダウン, part: back, places: [gym], sets: 3, reps: 10 }
22
+ - { name: ベントオーバーロウ, part: back, places: [gym], sets: 3, reps: 10 }
23
+ - { name: シーテッドロウ, part: back, places: [gym], sets: 3, reps: 10 }
24
+ - { name: デッドリフト, part: back, places: [gym], sets: 3, reps: 5 }
25
+
26
+ # --- 脚 ---
27
+ - { name: スクワット, part: legs, places: [home, gym], sets: 3, reps: 15 }
28
+ - { name: ブルガリアンスクワット, part: legs, places: [home, gym], sets: 3, reps: 10 }
29
+ - { name: ランジ, part: legs, places: [home, gym], sets: 3, reps: 12 }
30
+ - { name: カーフレイズ, part: legs, places: [home, gym], sets: 3, reps: 20 }
31
+ - { name: ヒップリフト, part: legs, places: [home, gym], sets: 3, reps: 15 }
32
+ - { name: ワイドスクワット, part: legs, places: [home], sets: 3, reps: 15 }
33
+ - { name: バーベルスクワット, part: legs, places: [gym], sets: 3, reps: 8 }
34
+ - { name: レッグプレス, part: legs, places: [gym], sets: 3, reps: 12 }
35
+ - { name: レッグカール, part: legs, places: [gym], sets: 3, reps: 12 }
36
+
37
+ # --- 肩 ---
38
+ - { name: パイクプッシュアップ, part: shoulders, places: [home], sets: 3, reps: 10 }
39
+ - { name: ショルダータップ, part: shoulders, places: [home], sets: 3, reps: 20 }
40
+ - { name: プランクアップダウン, part: shoulders, places: [home, gym], sets: 3, reps: 12 }
41
+ - { name: アームサークル, part: shoulders, places: [home], sets: 3, reps: 20 }
42
+ - { name: ショルダープレス, part: shoulders, places: [gym], sets: 3, reps: 10 }
43
+ - { name: サイドレイズ, part: shoulders, places: [gym], sets: 3, reps: 12 }
44
+ - { name: フロントレイズ, part: shoulders, places: [gym], sets: 3, reps: 12 }
45
+ - { name: リアレイズ, part: shoulders, places: [gym], sets: 3, reps: 12 }
46
+
47
+ # --- 腕 ---
48
+ - { name: ナロープッシュアップ, part: arms, places: [home, gym], sets: 3, reps: 12 }
49
+ - { name: ダイヤモンドプッシュアップ, part: arms, places: [home, gym], sets: 3, reps: 10 }
50
+ - { name: ベンチディップス, part: arms, places: [home, gym], sets: 3, reps: 12 }
51
+ - { name: アームカール, part: arms, places: [gym], sets: 3, reps: 12 }
52
+ - { name: ハンマーカール, part: arms, places: [gym], sets: 3, reps: 12 }
53
+ - { name: トライセプスエクステンション, part: arms, places: [gym], sets: 3, reps: 12 }
54
+ - { name: ケーブルプレスダウン, part: arms, places: [gym], sets: 3, reps: 12 }
55
+
56
+ # --- 腹筋 ---
57
+ - { name: クランチ, part: abs, places: [home, gym], sets: 3, reps: 20 }
58
+ - { name: レッグレイズ, part: abs, places: [home, gym], sets: 3, reps: 15 }
59
+ - { name: バイシクルクランチ, part: abs, places: [home, gym], sets: 3, reps: 20 }
60
+ - { name: プランク, part: abs, places: [home, gym], sets: 3, reps: 45, unit: 秒 }
61
+ - { name: サイドプランク, part: abs, places: [home, gym], sets: 3, reps: 30, unit: 秒 }
62
+ - { name: マウンテンクライマー, part: abs, places: [home, gym], sets: 3, reps: 30, unit: 秒 }
63
+ - { name: アブローラー, part: abs, places: [gym], sets: 3, reps: 10 }
64
+ - { name: ハンギングレッグレイズ, part: abs, places: [gym], sets: 3, reps: 10 }
65
+ - { name: ケーブルクランチ, part: abs, places: [gym], sets: 3, reps: 15 }
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkoutMenu
4
+ # 部位と場所からトレーニングメニューを提案する。
5
+ class Menu
6
+ def initialize(catalog: Catalog.load, random: Random.new)
7
+ @catalog = catalog
8
+ @random = random
9
+ end
10
+
11
+ # 条件に合う種目を、重複なしでランダムに count 個まで選ぶ。
12
+ def suggest(part:, place:, count: 4)
13
+ validate!(part, place, count)
14
+ @catalog.select(part: part, place: place).sample(count, random: @random)
15
+ end
16
+
17
+ private
18
+
19
+ def validate!(part, place, count)
20
+ raise Error, "unknown part: #{part}" unless Catalog::PARTS.key?(part)
21
+ raise Error, "unknown place: #{place}" unless Catalog::PLACES.key?(place)
22
+ raise Error, "count must be positive: #{count}" unless count.positive?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkoutMenu
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "workout_menu/version"
4
+
5
+ module WorkoutMenu
6
+ class Error < StandardError; end
7
+ end
8
+
9
+ require_relative "workout_menu/exercise"
10
+ require_relative "workout_menu/catalog"
11
+ require_relative "workout_menu/menu"
12
+ require_relative "workout_menu/cli"
@@ -0,0 +1,4 @@
1
+ module WorkoutMenu
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: workout_menu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - T-REX1996
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: workout_menu suggests a workout menu by body part and place, so you can
13
+ decide what to train without thinking.
14
+ email:
15
+ - bump12@icloud.com
16
+ executables:
17
+ - workout_menu
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE.txt
23
+ - README.md
24
+ - Rakefile
25
+ - exe/workout_menu
26
+ - lib/workout_menu.rb
27
+ - lib/workout_menu/catalog.rb
28
+ - lib/workout_menu/cli.rb
29
+ - lib/workout_menu/exercise.rb
30
+ - lib/workout_menu/exercises.yml
31
+ - lib/workout_menu/menu.rb
32
+ - lib/workout_menu/version.rb
33
+ - sig/workout_menu.rbs
34
+ homepage: https://github.com/T-REX1996/workout_menu
35
+ licenses:
36
+ - MIT
37
+ metadata:
38
+ source_code_uri: https://github.com/T-REX1996/workout_menu
39
+ changelog_uri: https://github.com/T-REX1996/workout_menu/blob/main/CHANGELOG.md
40
+ rubygems_mfa_required: 'true'
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 3.2.0
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 4.0.16
56
+ specification_version: 4
57
+ summary: A command-line tool that suggests a workout menu for your training day.
58
+ test_files: []