ichigo-ichie 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: eb3bb865cdda39e538053d20eb4e6570c5e33a261c78d0300623222de6a56d69
4
+ data.tar.gz: 27df509b554bdfe369ec8d6f891f91752db269410f8a57e98b47ba8c4852e31c
5
+ SHA512:
6
+ metadata.gz: 36ab28f324d77a19376b4d3a07e14dee299420bf7fbf1add17d69596eba66edd31c9ef3e6cbb9d736af02b71136a7c24d599a27584485bf62d1316e63c19504c
7
+ data.tar.gz: c1c6b8effbf08353fb5d04f0ddf427163e86a34f0b2ab5772f6b959138022578309761b66076b24dc3be4ccffb0e2fb0a6c36f40aa49c6fb9b7484efb7811d13
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sorted.
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # Ichigo Ichie
2
+
3
+ > **One day. One encounter. It will not come again.**
4
+ >
5
+ > 一期一会
6
+
7
+ `ichigo-ichie` is a terminal companion that shows one quote per day. Add it to your shell config. Open your terminal. Read one line. Start your day.
8
+
9
+ The same day always shows the same quote. A different day shows a different one. That's the point.
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Shell Integration](#shell-integration)
19
+ - [Commands](#commands)
20
+ - [Default (no command)](#default-no-command)
21
+ - [`tomorrow`](#tomorrow--peek-ahead)
22
+ - [`date`](#date--specific-date)
23
+ - [`add`](#add--add-a-quote)
24
+ - [`list`](#list--list-all-quotes)
25
+ - [`remove`](#remove--remove-a-quote)
26
+ - [`count`](#count--total-quotes)
27
+ - [`setup`](#setup--print-shell-instructions)
28
+ - [Quote Database](#quote-database)
29
+ - [Configuration & Data Location](#configuration--data-location)
30
+ - [Development](#development)
31
+ - [Related](#related)
32
+ - [License](#license)
33
+
34
+ ---
35
+
36
+ ## Features
37
+
38
+ | Feature | Description |
39
+ |---------|-------------|
40
+ | **One Quote Per Day** | Deterministic: same date = same quote. Different date = different quote. |
41
+ | **Shell Integration** | One line in `.bashrc` / `.zshrc`. Zero friction. |
42
+ | **120 Curated Quotes** | On impermanence, presence, and the unrepeatable nature of each moment. |
43
+ | **Custom Quotes** | Add, list, and remove your own. |
44
+ | **Zero Dependencies** | Ruby stdlib only (`pstore`, `date`). |
45
+ | **Offline Only** | No network. No accounts. No telemetry. |
46
+
47
+ ---
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ gem install ichigo-ichie
53
+ ```
54
+
55
+ ### Requirements
56
+
57
+ - Ruby >= 3.0.0
58
+
59
+ No external gems required.
60
+
61
+ ---
62
+
63
+ ## Quick Start
64
+
65
+ ```bash
66
+ ichigo
67
+ ```
68
+
69
+ That's it. You get one quote. Come back tomorrow for the next one.
70
+
71
+ ```text
72
+ $ ichigo
73
+ The tea cools whether you drink it or not.
74
+
75
+ $ ichigo
76
+ The tea cools whether you drink it or not.
77
+
78
+ (next day)
79
+ $ ichigo
80
+ You are not promised the evening.
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Shell Integration
86
+
87
+ Add one line to your shell config:
88
+
89
+ ### Bash (`~/.bashrc`)
90
+
91
+ ```bash
92
+ ichigo
93
+ ```
94
+
95
+ ### Zsh (`~/.zshrc`)
96
+
97
+ ```bash
98
+ ichigo
99
+ ```
100
+
101
+ ### Fish (`~/.config/fish/config.fish`)
102
+
103
+ ```fish
104
+ ichigo
105
+ ```
106
+
107
+ Every time you open a new terminal, you see one line. That's all.
108
+
109
+ ---
110
+
111
+ ## Commands
112
+
113
+ ### Default (no command)
114
+
115
+ ```bash
116
+ ichigo
117
+ ```
118
+
119
+ Shows today's quote. This is the primary use case.
120
+
121
+ ### `tomorrow` — Peek Ahead
122
+
123
+ ```bash
124
+ ichigo tomorrow
125
+ ```
126
+
127
+ See what tomorrow holds. Useful if you are impatient, but that defeats the purpose.
128
+
129
+ ### `date` — Specific Date
130
+
131
+ ```bash
132
+ ichigo date 2026-12-25
133
+ ```
134
+
135
+ Show the quote assigned to any date. Format: `YYYY-MM-DD`.
136
+
137
+ ### `add` — Add a Quote
138
+
139
+ ```bash
140
+ ichigo add "Your custom quote here."
141
+ ```
142
+
143
+ - Maximum **200 characters**.
144
+ - Duplicates are rejected.
145
+
146
+ ### `list` — List All Quotes
147
+
148
+ ```bash
149
+ ichigo list
150
+ ```
151
+
152
+ Shows all quotes with their IDs.
153
+
154
+ ### `remove` — Remove a Quote
155
+
156
+ ```bash
157
+ ichigo remove 42
158
+ ```
159
+
160
+ Delete a quote by its ID.
161
+
162
+ ### `count` — Total Quotes
163
+
164
+ ```bash
165
+ ichigo count
166
+ ```
167
+
168
+ Shows how many quotes are in the database.
169
+
170
+ ### `setup` — Print Shell Instructions
171
+
172
+ ```bash
173
+ ichigo setup
174
+ ```
175
+
176
+ Prints the lines to add to your shell config.
177
+
178
+ ---
179
+
180
+ ## Quote Database
181
+
182
+ Quotes are stored in a local PStore database at `~/.config/ichigo-ichie/quotes.db`.
183
+
184
+ On first run, the database is seeded with 120 curated quotes about:
185
+
186
+ - **Impermanence** — things pass, whether you notice or not
187
+ - **Presence** — what is in front of you right now
188
+ - **Mortality** — you will not get a cleaner draft of today
189
+ - **Mono no aware** (物の哀れ) — the bittersweet awareness of transience
190
+
191
+ The day-of-year (1-366) determines which quote is shown. The mapping wraps around the quote pool, so every day always maps to exactly one quote.
192
+
193
+ Adding or removing quotes changes the pool, which may shift which quote appears on which day.
194
+
195
+ ---
196
+
197
+ ## Configuration & Data Location
198
+
199
+ | Platform | Path |
200
+ |----------|------|
201
+ | Linux / macOS | `~/.config/ichigo-ichie/quotes.db` |
202
+ | Windows | `%USERPROFILE%\.config\ichigo-ichie\quotes.db` |
203
+
204
+ The directory is created automatically on first run.
205
+
206
+ ---
207
+
208
+ ## Development
209
+
210
+ ### Setup
211
+
212
+ ```bash
213
+ git clone https://github.com/YumaKakuya/ichigo-ichie.git
214
+ cd ichigo-ichie
215
+ ```
216
+
217
+ ### Running Locally
218
+
219
+ ```bash
220
+ ruby -Ilib bin/ichigo
221
+ ```
222
+
223
+ ### Building the Gem
224
+
225
+ ```bash
226
+ gem build ichigo-ichie.gemspec
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Related
232
+
233
+ - [zen-and-musashi](https://github.com/YumaKakuya/zenandmusashi) — Japanese wisdom for your terminal
234
+ - [ronin](https://github.com/YumaKakuya/ronin) — Focus timer for the solo developer
235
+
236
+ ---
237
+
238
+ ## License
239
+
240
+ MIT License. See [LICENSE](LICENSE) for the full text.
241
+
242
+ ---
243
+
244
+ > *This hour will not come again.*
data/bin/ichigo ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.join(__dir__, "..", "lib"))
5
+
6
+ require "ichigo_ichie"
7
+
8
+ IchigoIchie::CLI.start(ARGV)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/ichigo_ichie/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ichigo-ichie'
7
+ spec.version = IchigoIchie::VERSION
8
+ spec.authors = ['Sorted.']
9
+ spec.email = []
10
+
11
+ spec.summary = 'One day. One encounter. It will not come again.'
12
+ spec.description = "ichigo-ichie is a terminal companion that shows one quote per day. 120 curated quotes about impermanence, presence, and the unrepeatable nature of each moment. Add it to your shell config. That's it."
13
+ spec.homepage = 'https://github.com/YumaKakuya/ichigo-ichie'
14
+ spec.license = 'MIT'
15
+
16
+ spec.required_ruby_version = '>= 3.0.0'
17
+
18
+ spec.files = Dir.glob('{bin,lib}/**/*') + %w[README.md LICENSE ichigo-ichie.gemspec]
19
+ spec.bindir = 'bin'
20
+ spec.executables = ['ichigo']
21
+ spec.require_paths = ['lib']
22
+
23
+ # No runtime dependencies — stdlib only
24
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative 'store'
5
+ require_relative 'seed'
6
+
7
+ module IchigoIchie
8
+ module Calendar
9
+ module_function
10
+
11
+ def quote_for(date, store)
12
+ Seed.run(store) if store.count.zero?
13
+
14
+ day_index = date.yday - 1
15
+ store.get(day_index)
16
+ end
17
+
18
+ def today(store)
19
+ quote_for(Date.today, store)
20
+ end
21
+
22
+ def tomorrow(store)
23
+ quote_for(Date.today + 1, store)
24
+ end
25
+
26
+ def for_date(date_str, store)
27
+ date = Date.parse(date_str)
28
+ quote_for(date, store)
29
+ rescue Date::Error
30
+ raise "Invalid date: '#{date_str}'. Use YYYY-MM-DD format."
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative 'version'
5
+ require_relative 'store'
6
+ require_relative 'calendar'
7
+
8
+ module IchigoIchie
9
+ class CLI
10
+ BANNER = 'One day. One encounter. It will not come again.'
11
+
12
+ def self.start(argv)
13
+ new.run(argv)
14
+ end
15
+
16
+ def run(argv)
17
+ command = argv.shift
18
+
19
+ # Default: show today's quote
20
+ if command.nil?
21
+ handle_today
22
+ return
23
+ end
24
+
25
+ case command
26
+ when '-h', '--help'
27
+ puts help
28
+ exit 0
29
+ when '-v', '--version'
30
+ puts "ichigo #{IchigoIchie::VERSION}"
31
+ exit 0
32
+ when 'today'
33
+ handle_today
34
+ when 'tomorrow'
35
+ handle_tomorrow
36
+ when 'date'
37
+ handle_date(argv)
38
+ when 'add'
39
+ handle_add(argv)
40
+ when 'list'
41
+ handle_list
42
+ when 'remove'
43
+ handle_remove(argv)
44
+ when 'count'
45
+ handle_count
46
+ when 'setup'
47
+ handle_setup
48
+ else
49
+ warn "Unknown command: '#{command}'"
50
+ puts help
51
+ exit 1
52
+ end
53
+ rescue StandardError => e
54
+ warn "Error: #{e.message}"
55
+ exit 1
56
+ end
57
+
58
+ private
59
+
60
+ def help
61
+ <<~HELP
62
+ #{BANNER}
63
+
64
+ Usage: ichigo [command]
65
+
66
+ Commands:
67
+ (no command) Show today's quote (default)
68
+ today Show today's quote
69
+ tomorrow Peek at tomorrow's quote
70
+ date YYYY-MM-DD Show quote for a specific date
71
+ add <quote> Add a custom quote
72
+ list List all quotes
73
+ remove <id> Remove a quote by ID
74
+ count Show total quote count
75
+ setup Print shell setup instructions
76
+
77
+ Options:
78
+ -v, --version Show version
79
+ -h, --help Show this help message
80
+
81
+ Shell integration:
82
+ Add to your ~/.bashrc or ~/.zshrc:
83
+ ichigo
84
+
85
+ Examples:
86
+ ichigo # today's quote
87
+ ichigo tomorrow # peek at tomorrow
88
+ ichigo date 2026-12-25 # quote for Christmas
89
+ ichigo add "Your quote." # add a custom quote
90
+ ichigo list # list all quotes
91
+ HELP
92
+ end
93
+
94
+ def handle_today
95
+ quote = Calendar.today(store)
96
+ if quote
97
+ puts quote[:text]
98
+ else
99
+ puts 'No quotes in the database.'
100
+ end
101
+ end
102
+
103
+ def handle_tomorrow
104
+ quote = Calendar.tomorrow(store)
105
+ if quote
106
+ puts quote[:text]
107
+ else
108
+ puts 'No quotes in the database.'
109
+ end
110
+ end
111
+
112
+ def handle_date(argv)
113
+ date_str = argv.shift
114
+ if date_str.nil? || date_str.strip.empty?
115
+ warn 'Error: Date is required.'
116
+ puts 'Usage: ichigo date YYYY-MM-DD'
117
+ exit 1
118
+ end
119
+
120
+ quote = Calendar.for_date(date_str, store)
121
+ if quote
122
+ puts quote[:text]
123
+ else
124
+ puts 'No quotes in the database.'
125
+ end
126
+ end
127
+
128
+ def handle_add(argv)
129
+ text = argv.join(' ').strip
130
+
131
+ if text.empty?
132
+ warn 'Error: Quote text is required.'
133
+ puts 'Usage: ichigo add "your quote"'
134
+ exit 1
135
+ end
136
+
137
+ id = store.add(text)
138
+ puts "Added quote ##{id}. Total: #{store.count}"
139
+ end
140
+
141
+ def handle_list
142
+ quotes = store.all
143
+
144
+ if quotes.empty?
145
+ puts 'No quotes. Add one with: ichigo add "your quote"'
146
+ return
147
+ end
148
+
149
+ puts "#{quotes.length} quotes:"
150
+ puts '-' * 36
151
+ quotes.each do |q|
152
+ puts " ##{q[:id]}: #{q[:text]}"
153
+ end
154
+ end
155
+
156
+ def handle_remove(argv)
157
+ id_str = argv.shift
158
+ if id_str.nil? || id_str.strip.empty?
159
+ warn 'Error: Quote ID is required.'
160
+ puts 'Usage: ichigo remove <id>'
161
+ exit 1
162
+ end
163
+
164
+ begin
165
+ id = Integer(id_str)
166
+ rescue ArgumentError
167
+ warn 'Error: ID must be a number.'
168
+ exit 1
169
+ end
170
+
171
+ store.remove(id)
172
+ puts "Removed quote ##{id}. Total: #{store.count}"
173
+ end
174
+
175
+ def handle_count
176
+ puts "#{store.count} quotes in the database."
177
+ end
178
+
179
+ def handle_setup
180
+ puts <<~SETUP
181
+ Add one of the following lines to your shell config:
182
+
183
+ # For bash (~/.bashrc):
184
+ ichigo
185
+
186
+ # For zsh (~/.zshrc):
187
+ ichigo
188
+
189
+ # For fish (~/.config/fish/config.fish):
190
+ ichigo
191
+
192
+ That's it. One line. One quote per day.
193
+ SETUP
194
+ end
195
+
196
+ def store
197
+ @store ||= Store.new
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "store"
4
+
5
+ module IchigoIchie
6
+ module Seed
7
+ DEFAULT_QUOTES = [
8
+ "This hour will not come again.",
9
+ "The tea cools whether you drink it or not.",
10
+ "You are not promised the evening.",
11
+ "Smoke rising from the incense. That is all the time there is.",
12
+ "The crack in the bowl is where the light has always been.",
13
+ "No one steps into the same conversation twice.",
14
+ "What you are postponing, you are abandoning.",
15
+ "The last time you held your child, you didn't know it was the last time.",
16
+ "Snow on the temple bell.",
17
+ "Even the stone steps are wearing away beneath your feet.",
18
+ "This face across the table: study it now.",
19
+ "The fruit is already falling.",
20
+ "A day spent entirely indoors is still a day spent.",
21
+ "It is not that life is short. It is that you are dead for so very long.",
22
+ "While we wait for life, life passes.",
23
+ "You could leave life right now. Let that determine what you do and say and think.",
24
+ "The present moment is a needle's eye through which you are always passing.",
25
+ "Every letter you write might be your last.",
26
+ "Dawn does not repeat its colors.",
27
+ "The conversation you keep meaning to have has an expiration date.",
28
+ "Nothing is yours except the present thin slice of time.",
29
+ "The morning glories bloomed while you were checking your phone.",
30
+ "A falling leaf does not negotiate with the wind.",
31
+ "You will eat a certain number of meals in your life. This is one of them.",
32
+ "How many Octobers do you have left? Fewer than you think.",
33
+ "The water in your glass was rain this morning.",
34
+ "Someone is hearing your voice for the last time today.",
35
+ "The room after the guests have gone still holds the shape of laughter.",
36
+ "No rehearsal. No second take.",
37
+ "The candle does not grieve its own wax.",
38
+ "Treat each meeting as though you will never meet this person again. You might not.",
39
+ "Your hands are older than they were when you started reading this.",
40
+ "The bird on the wire will not be there when you look again.",
41
+ "Loss is not something that happens to a life. Loss is what a life is made of.",
42
+ "Every homecoming is also a leave-taking in disguise.",
43
+ "Dusk asks nothing. It simply arrives.",
44
+ "The bridge you cross daily is slowly becoming a different bridge.",
45
+ "We do not see the same moon twice.",
46
+ "The sword cuts. The flower falls. Neither can be taken back.",
47
+ "Already the hour is late. It was always late.",
48
+ "There is no ordinary moment. You have just stopped looking.",
49
+ "Sit with what is here before it is not.",
50
+ "Even stone is on its way to sand.",
51
+ "Rain on the window has never made this exact pattern before.",
52
+ "You will set down your last cup of tea without knowing it is the last.",
53
+ "The afternoon light on that wall will never fall at precisely this angle again.",
54
+ "Time is not a thing you spend. It is a thing that spends you.",
55
+ "The dog does not know this walk is different from any other. And yet it is.",
56
+ "Autumn teaches without speaking.",
57
+ "Someone you love is breathing right now. Listen.",
58
+ "A conversation with the dead is just memory rehearsing in an empty room.",
59
+ "What the river carries does not return upstream.",
60
+ "The bell has already been struck. You are hearing the silence after.",
61
+ "Every handshake is a small goodbye.",
62
+ "Hold the cup with both hands. Feel the warmth while it is warm.",
63
+ "The photograph is already a lie about how still things were.",
64
+ "Wind through the pines. It will not say this again.",
65
+ "Old letters prove that even your handwriting was someone else's.",
66
+ "The season will not wait for your readiness.",
67
+ "There are people you will never think of again. Today might be the day.",
68
+ "What is solid is mostly empty space pretending.",
69
+ "The last page is already bound into the book.",
70
+ "Fog does not announce when it will lift.",
71
+ "You have already forgotten most of your life.",
72
+ "The plum blossom falls. The branch does not reach for it.",
73
+ "Nothing is more urgent than what is in front of you right now.",
74
+ "We speak of killing time as though it were not the reverse.",
75
+ "This version of your city exists only today.",
76
+ "The ink dries and the meaning is fixed, but the writer has already changed.",
77
+ "The friends you made at twenty are strangers wearing familiar faces.",
78
+ "A single afternoon contains more than you will ever extract from it.",
79
+ "Everything you own will fit in other people's hands.",
80
+ "Morning fog on the lake: gone before you can describe it.",
81
+ "The threshold knows every coming and going, and says nothing.",
82
+ "The meal tasted different because you were paying attention.",
83
+ "Water holds no shape of its own. Neither does a day.",
84
+ "You pass your own ghost on streets you used to walk.",
85
+ "The crack of ice is spring arriving without permission.",
86
+ "What you did not say today cannot be said today.",
87
+ "The same sky, but you are not the same eyes.",
88
+ "You are the only one who will live this specific Tuesday.",
89
+ "That which cannot be repaired can still be beautiful.",
90
+ "The dust on old books is a kind of silence.",
91
+ "Nothing happens the same way once you know it will end.",
92
+ "An empty chair was once enough for someone.",
93
+ "The sound of rain is never the same sound twice.",
94
+ "The kintsugi bowl does not pretend it was never broken.",
95
+ "The temple has burned. The garden remains.",
96
+ "Tea poured slowly fills the cup at the same rate.",
97
+ "Each breath is a door that opens only once and closes behind you.",
98
+ "What is heavy today will be invisible in a decade.",
99
+ "A sunset no one watches still sets.",
100
+ "The face in old photographs is not lying. You have simply moved on.",
101
+ "Close the book gently. It held something for a while.",
102
+ "Somewhere a clock you wound is still running without you.",
103
+ "Nothing blooms for long. That is why blooming matters.",
104
+ "The conversation you are avoiding is the conversation your life is about.",
105
+ "The carpenter measures twice because wood does not unmake a cut.",
106
+ "Not every ending announces itself. Most just stop.",
107
+ "This weather will never happen again.",
108
+ "You will not get a cleaner draft of today.",
109
+ "Even mountains are in transit. They are just unhurried about it.",
110
+ "Last night's dream is already another country.",
111
+ "The warmth on your back is a star doing its slow dying.",
112
+ "A letter unsent is a conversation only you attended.",
113
+ "The moss grows over every name eventually.",
114
+ "Snow falls on the just and the unjust alike, and melts the same.",
115
+ "There is no later. There is only this, rearranged.",
116
+ "The light changes. The room forgets its earlier self.",
117
+ "The train left on time. Whether you were on it is your affair.",
118
+ "The broken pot still holds the shape of what it carried.",
119
+ "An unvisited friend is a conversation slowly going cold.",
120
+ "The wave does not return to ask if you were watching.",
121
+ "Everything you build is a letter to someone you will not meet.",
122
+ "Nothing in this room was here a hundred years ago. Nothing will remain.",
123
+ "It takes a lifetime to learn to sit still for an hour.",
124
+ "The match does not light twice.",
125
+ "A still pond is not the same as an empty one.",
126
+ "The old man plants a tree whose shade he will never sit in.",
127
+ "Ash remembers fire the way the living remember the dead — by shape alone.",
128
+ ].freeze
129
+
130
+ def self.run(store)
131
+ store.seed(DEFAULT_QUOTES)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pstore'
4
+ require 'fileutils'
5
+
6
+ module IchigoIchie
7
+ class Store
8
+ DB_DIR = File.join(Dir.home, '.config', 'ichigo-ichie')
9
+ DB_PATH = File.join(DB_DIR, 'quotes.db')
10
+
11
+ def initialize
12
+ FileUtils.mkdir_p(DB_DIR)
13
+ @pstore = PStore.new(DB_PATH)
14
+ end
15
+
16
+ def all
17
+ @pstore.transaction(true) do
18
+ @pstore['quotes'] || []
19
+ end
20
+ end
21
+
22
+ def count
23
+ all.length
24
+ end
25
+
26
+ def get(index)
27
+ quotes = all
28
+ return nil if quotes.empty?
29
+
30
+ quotes[index % quotes.length]
31
+ end
32
+
33
+ def add(text)
34
+ @pstore.transaction do
35
+ quotes = @pstore['quotes'] || []
36
+
37
+ raise 'That quote already exists.' if quotes.any? { |q| q[:text].casecmp(text).zero? }
38
+
39
+ raise 'Quote is too long. Maximum 200 characters.' if text.length > 200
40
+
41
+ new_id = (quotes.map { |q| q[:id] }.max || 0) + 1
42
+ quotes << { id: new_id, text: text }
43
+ @pstore['quotes'] = quotes
44
+
45
+ new_id
46
+ end
47
+ end
48
+
49
+ def remove(id)
50
+ @pstore.transaction do
51
+ quotes = @pstore['quotes'] || []
52
+ idx = quotes.index { |q| q[:id] == id }
53
+ raise "Quote ID #{id} not found." unless idx
54
+
55
+ quotes.delete_at(idx)
56
+ @pstore['quotes'] = quotes
57
+ true
58
+ end
59
+ end
60
+
61
+ def seed(quotes_array)
62
+ @pstore.transaction do
63
+ existing = @pstore['quotes'] || []
64
+
65
+ quotes_array.each do |text|
66
+ next if existing.any? { |q| q[:text].casecmp(text).zero? }
67
+
68
+ new_id = (existing.map { |q| q[:id] }.max || 0) + 1
69
+ existing << { id: new_id, text: text }
70
+ end
71
+
72
+ @pstore['quotes'] = existing
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IchigoIchie
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ichigo_ichie/version'
4
+ require_relative 'ichigo_ichie/store'
5
+ require_relative 'ichigo_ichie/seed'
6
+ require_relative 'ichigo_ichie/calendar'
7
+ require_relative 'ichigo_ichie/cli'
8
+
9
+ module IchigoIchie
10
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ichigo-ichie
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sorted.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-23 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ichigo-ichie is a terminal companion that shows one quote per day. 120
14
+ curated quotes about impermanence, presence, and the unrepeatable nature of each
15
+ moment. Add it to your shell config. That's it.
16
+ email: []
17
+ executables:
18
+ - ichigo
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - bin/ichigo
25
+ - ichigo-ichie.gemspec
26
+ - lib/ichigo_ichie.rb
27
+ - lib/ichigo_ichie/calendar.rb
28
+ - lib/ichigo_ichie/cli.rb
29
+ - lib/ichigo_ichie/seed.rb
30
+ - lib/ichigo_ichie/store.rb
31
+ - lib/ichigo_ichie/version.rb
32
+ homepage: https://github.com/YumaKakuya/ichigo-ichie
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 3.0.0
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.4.1
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: One day. One encounter. It will not come again.
55
+ test_files: []