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 +7 -0
- data/LICENSE +21 -0
- data/README.md +244 -0
- data/bin/ichigo +8 -0
- data/ichigo-ichie.gemspec +24 -0
- data/lib/ichigo_ichie/calendar.rb +33 -0
- data/lib/ichigo_ichie/cli.rb +200 -0
- data/lib/ichigo_ichie/seed.rb +134 -0
- data/lib/ichigo_ichie/store.rb +76 -0
- data/lib/ichigo_ichie/version.rb +5 -0
- data/lib/ichigo_ichie.rb +10 -0
- metadata +55 -0
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,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
|
data/lib/ichigo_ichie.rb
ADDED
|
@@ -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: []
|