journal-cli 1.0.22 → 1.0.24
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/README.md +17 -2
- data/bin/journal +5 -1
- data/lib/journal-cli/checkin.rb +36 -11
- data/lib/journal-cli/question.rb +10 -2
- data/lib/journal-cli/section.rb +4 -3
- data/lib/journal-cli/string.rb +27 -0
- data/lib/journal-cli/version.rb +1 -1
- data/lib/journal-cli/weather.rb +10 -2
- data/lib/journal-cli.rb +3 -0
- data/src/_README.md +17 -2
- 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: 110b0fe8610e39052c3f85c417739821f66103cd0457a23a03188a158062962f
|
4
|
+
data.tar.gz: b56dfb4649456dd53e22aa86fece669f8d162fd6048537e06ba8c6aeb0db61c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 237a1faa0d7d4acfb0bb1d0c3775b46055529e432dbba25e38b723cd5cab39e68db7796d824c870739f8fa9b28f2c1d040df1d4fb84bd3e57581cc18ba931ea7
|
7
|
+
data.tar.gz: cc8e68cf73df3bf13a71b2eb9126a136dd9f989a869c519dece7f733e1a89dfd5552626fc249bf7c6bc2ea95ee2a1a23836a6c8ed410202b1d4578b6be8d655e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
### 1.0.24
|
2
|
+
|
3
|
+
2023-09-20 09:12
|
4
|
+
|
5
|
+
### 1.0.23
|
6
|
+
|
7
|
+
2023-09-20 08:30
|
8
|
+
|
9
|
+
#### NEW
|
10
|
+
|
11
|
+
- Question types weather.forecast and weather.current allow more specific weather entry types
|
12
|
+
- Time-based conditions for questions and sections (`condition: before noon` or `condition: < 12pm`)
|
13
|
+
|
14
|
+
#### FIXED
|
15
|
+
|
16
|
+
- Test for existence of dayone2 binary before attempting to write a Day One entry, provide error message
|
17
|
+
|
1
18
|
### 1.0.22
|
2
19
|
|
3
20
|
2023-09-18 10:23
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -52,6 +52,10 @@ This file contains a YAML definition of your journal. Each journal gets a top-le
|
|
52
52
|
|
53
53
|
You can include weather data automatically by setting a question type to 'weather'. In order for this to work, you'll need to define `zip` and `weather_api` keys. `zip` is just your zip code, and `weather_api` is a key from WeatherAPI.com. Sign up [here](https://www.weatherapi.com/) for a free plan, and then visit the [profile page](https://www.weatherapi.com/my/) to see your API key at the top.
|
54
54
|
|
55
|
+
If a question type is set to `weather.forecast`, only the predicted condition, high, and low will be included in the JSON data for the question. A full printout of hourly temps will be included in the Markdown/Day One output.
|
56
|
+
|
57
|
+
If the question type is `weather.current`, only the current condition and temperature will be recorded to the JSON, and a string containing "[TEMP] and [CONDITION]" (e.g. "64 and Sunny") will be recorded to Markdown/Day One for the question.
|
58
|
+
|
55
59
|
### Journal Configuration
|
56
60
|
|
57
61
|
Edit the file at `~/.config/journal/journals.yaml` following this structure:
|
@@ -96,9 +100,12 @@ journals: # required key
|
|
96
100
|
- title: Weather # Title of the section (will create template sections in Day One)
|
97
101
|
key: weather # the key to use in the structured data, will contain all of the answers
|
98
102
|
questions: # required key
|
99
|
-
- prompt: Current
|
103
|
+
- prompt: Current Weather
|
104
|
+
key: weather.current
|
105
|
+
type: weather.current
|
106
|
+
- prompt: Weather Forecast # The prompt shown on the command line, will also become a header in the journal entries (Markdown, Day One)
|
100
107
|
key: weather.forecast # if a key contains a dot, it will create nested data, e.g. `{ 'weather': { 'forecast': data } }`
|
101
|
-
type: weather # Set this to weather for weather data
|
108
|
+
type: weather.forecast # Set this to weather for weather data
|
102
109
|
- title: Health # New section
|
103
110
|
key: health
|
104
111
|
questions:
|
@@ -141,10 +148,18 @@ A question `type` can be one of:
|
|
141
148
|
- `text` or `string` will request a single-line string, submitted on return
|
142
149
|
- `multiline` for multiline strings (opens a readline editor, use ctrl-d to save)
|
143
150
|
- `weather` will just insert current weather data with no prompt
|
151
|
+
* `weather.forecast` will insert just the forecast
|
152
|
+
* `weather.current` will insert just the current temperature and condition
|
144
153
|
- `number` or `float` will request numeric input, stored as a float (decimal)
|
145
154
|
- `integer` will convert numeric input to the nearest integer
|
146
155
|
- `date` will request a natural language date which will be parsed into a date object
|
147
156
|
|
157
|
+
### Conditional Questions
|
158
|
+
|
159
|
+
You can have a question only show up based on conditions. Currently the only condition is time based. Just add a key called `condition` to the question definition, then include a natural language string like `before noon` or `after 3pm`. If the condition is matched, then the question will be displayed, otherwise it will be skipped and its data entry in the JSON will be null.
|
160
|
+
|
161
|
+
Conditions can be applied to individual questions, or to entire sections, depending on where the `condition` key is placed.
|
162
|
+
|
148
163
|
### Naming Keys
|
149
164
|
|
150
165
|
If you want data stored in a nested object, you can set a question type to `dictionary` and set the prompt to `null` (or just leave the key out), but give it a key that will serve as the parent in the object. Then in the nested questions, give them a key in the dot format `[PARENT_KEY].[CHILD_KEY]`. Section keys automatically nest their questions, but if you want to go deeper, you could have a question with the key `health` and type `dictionary`, then have questions with keys like `health.rating` and `health.notes`. If the section key was `status`, the resulting dictionary would look like this in the JSON:
|
data/bin/journal
CHANGED
@@ -4,6 +4,8 @@ $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
|
|
4
4
|
require 'journal-cli'
|
5
5
|
require 'optparse'
|
6
6
|
|
7
|
+
trap('SIGINT') { exit! }
|
8
|
+
|
7
9
|
module Journal
|
8
10
|
class << self
|
9
11
|
def usage
|
@@ -33,8 +35,10 @@ module Journal
|
|
33
35
|
Time.now
|
34
36
|
end
|
35
37
|
|
38
|
+
Journal.date = date
|
39
|
+
|
36
40
|
if Journal.config['journals'].key?(journal)
|
37
|
-
checkin = Journal::Checkin.new(journal
|
41
|
+
checkin = Journal::Checkin.new(journal)
|
38
42
|
checkin.go
|
39
43
|
else
|
40
44
|
puts "Journal #{journal} not found"
|
data/lib/journal-cli/checkin.rb
CHANGED
@@ -3,10 +3,10 @@ module Journal
|
|
3
3
|
class Checkin
|
4
4
|
attr_reader :key, :date, :data, :config, :journal, :sections, :title, :output
|
5
5
|
|
6
|
-
def initialize(journal
|
6
|
+
def initialize(journal)
|
7
7
|
@key = journal
|
8
8
|
@output = []
|
9
|
-
@date = date
|
9
|
+
@date = Journal.date
|
10
10
|
@date.localtime
|
11
11
|
|
12
12
|
raise StandardError, "No journal with key #{@key} found" unless Journal.config['journals'].key? @key
|
@@ -36,7 +36,7 @@ module Journal
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def hr
|
39
|
-
@output << "\n
|
39
|
+
@output << "\n* * * * * *\n"
|
40
40
|
end
|
41
41
|
|
42
42
|
def go
|
@@ -58,6 +58,11 @@ module Journal
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def save_day_one_entry
|
61
|
+
unless TTY::Which.exist?('dayone2')
|
62
|
+
Journal.notify('{br}Day One CLI not installed, no Day One entry created')
|
63
|
+
return
|
64
|
+
end
|
65
|
+
@date.localtime
|
61
66
|
cmd = ['dayone2']
|
62
67
|
cmd << %(-j "#{@journal['journal']}") if @journal.key?('journal')
|
63
68
|
cmd << %(-t #{@journal['tags'].join(' ')}) if @journal.key?('tags')
|
@@ -128,11 +133,17 @@ module Journal
|
|
128
133
|
end
|
129
134
|
|
130
135
|
def print_answer(prompt, type, key, data)
|
136
|
+
return if data.nil? || !data.key?(key)
|
137
|
+
|
131
138
|
case type
|
132
139
|
when /^(weather|forecast)/
|
133
140
|
header prompt
|
134
|
-
@output <<
|
135
|
-
|
141
|
+
@output << case type
|
142
|
+
when /current$/
|
143
|
+
data[key].current
|
144
|
+
else
|
145
|
+
data[key].to_markdown
|
146
|
+
end
|
136
147
|
when /^(int|num)/
|
137
148
|
@output << "#{prompt}: #{data[key]} " unless data[key].nil?
|
138
149
|
when /^date/
|
@@ -158,7 +169,14 @@ module Journal
|
|
158
169
|
v.localtime
|
159
170
|
data[k] = v.strftime('%Y-%m-%d %H:%M')
|
160
171
|
when /Weather/
|
161
|
-
data[k] =
|
172
|
+
data[k] = case k
|
173
|
+
when /current$/
|
174
|
+
v.current
|
175
|
+
when /forecast$/
|
176
|
+
data[k] = v.forecast
|
177
|
+
else
|
178
|
+
data[k] = v.to_s
|
179
|
+
end
|
162
180
|
else
|
163
181
|
data[k] = v
|
164
182
|
end
|
@@ -240,11 +258,18 @@ module Journal
|
|
240
258
|
v.each do |key, value|
|
241
259
|
result = case value.class.to_s
|
242
260
|
when /Weather/
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
261
|
+
if key =~ /current$/
|
262
|
+
{
|
263
|
+
'temp' => value.data[:temp],
|
264
|
+
'condition' => value.data[:current_condition]
|
265
|
+
}
|
266
|
+
else
|
267
|
+
{
|
268
|
+
'high' => value.data[:high],
|
269
|
+
'low' => value.data[:low],
|
270
|
+
'condition' => value.data[:condition]
|
271
|
+
}
|
272
|
+
end
|
248
273
|
else
|
249
274
|
value
|
250
275
|
end
|
data/lib/journal-cli/question.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Journal
|
4
4
|
# Individual question
|
5
5
|
class Question
|
6
|
-
attr_reader :key, :type, :min, :max, :prompt, :secondary_prompt, :gum
|
6
|
+
attr_reader :key, :type, :min, :max, :prompt, :secondary_prompt, :gum, :condition
|
7
7
|
|
8
8
|
##
|
9
9
|
## Initializes the given question.
|
@@ -20,6 +20,7 @@ module Journal
|
|
20
20
|
@prompt = question['prompt'] || nil
|
21
21
|
@secondary_prompt = question['secondary_prompt'] || nil
|
22
22
|
@gum = TTY::Which.exist?('gum')
|
23
|
+
@condition = question.key?('condition') ? question['condition'].parse_condition : true
|
23
24
|
end
|
24
25
|
|
25
26
|
##
|
@@ -27,9 +28,11 @@ module Journal
|
|
27
28
|
##
|
28
29
|
## @return [Number, String] the response based on @type
|
29
30
|
##
|
30
|
-
def ask
|
31
|
+
def ask(condition)
|
31
32
|
return nil if @prompt.nil?
|
32
33
|
|
34
|
+
return nil unless @condition && condition
|
35
|
+
|
33
36
|
case @type
|
34
37
|
when /^int/i
|
35
38
|
read_number(integer: true)
|
@@ -126,6 +129,7 @@ module Journal
|
|
126
129
|
##
|
127
130
|
##
|
128
131
|
def read_number_gum
|
132
|
+
trap('SIGINT') { exit! }
|
129
133
|
res = `gum input --placeholder "#{@min}-#{@max}"`.strip
|
130
134
|
return nil if res.strip.empty?
|
131
135
|
|
@@ -140,6 +144,7 @@ module Journal
|
|
140
144
|
## @return [Number] integer response
|
141
145
|
##
|
142
146
|
def read_line_tty
|
147
|
+
trap('SIGINT') { exit! }
|
143
148
|
reader = TTY::Reader.new
|
144
149
|
res = reader.read_line('>> ')
|
145
150
|
return nil if res.strip.empty?
|
@@ -155,6 +160,7 @@ module Journal
|
|
155
160
|
## @return [Number] integer response
|
156
161
|
##
|
157
162
|
def read_line_gum(prompt)
|
163
|
+
trap('SIGINT') { exit! }
|
158
164
|
`gum input --placeholder "#{prompt} (blank to end answer)"`
|
159
165
|
end
|
160
166
|
|
@@ -164,6 +170,7 @@ module Journal
|
|
164
170
|
## @return [string] multiline input
|
165
171
|
##
|
166
172
|
def read_mutliline_tty
|
173
|
+
trap('SIGINT') { exit! }
|
167
174
|
reader = TTY::Reader.new
|
168
175
|
res = reader.read_multiline
|
169
176
|
res.join("\n")
|
@@ -175,6 +182,7 @@ module Journal
|
|
175
182
|
## @return [string] multiline input
|
176
183
|
##
|
177
184
|
def read_multiline_gum(prompt)
|
185
|
+
trap('SIGINT') { exit! }
|
178
186
|
`gum write --placeholder "#{prompt}" --width 80 --char-limit 0`
|
179
187
|
end
|
180
188
|
end
|
data/lib/journal-cli/section.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Journal
|
4
4
|
class Section
|
5
|
-
attr_accessor :key, :title, :questions, :answers
|
5
|
+
attr_accessor :key, :title, :questions, :answers, :condition
|
6
6
|
|
7
7
|
##
|
8
8
|
## Initializes the given section.
|
@@ -15,6 +15,7 @@ module Journal
|
|
15
15
|
def initialize(section)
|
16
16
|
@key = section['key']
|
17
17
|
@title = section['title']
|
18
|
+
@condition = section.key?('condition') ? section['condition'].parse_condition : true
|
18
19
|
@questions = section['questions'].map { |question| Question.new(question) }
|
19
20
|
@questions.delete_if { |q| q.prompt.nil? }
|
20
21
|
@answers = {}
|
@@ -38,9 +39,9 @@ module Journal
|
|
38
39
|
res = res[key]
|
39
40
|
end
|
40
41
|
|
41
|
-
res[keys.last] = question.ask
|
42
|
+
res[keys.last] = question.ask(@condition)
|
42
43
|
else
|
43
|
-
@answers[question.key] = question.ask
|
44
|
+
@answers[question.key] = question.ask(@condition)
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# String helpers
|
4
|
+
class ::String
|
5
|
+
def parse_condition
|
6
|
+
condition = dup
|
7
|
+
time_rx = /(?<comp>[<>=]{1,2}|before|after) +(?<time>(?:noon|midnight|[0-9]+) *(?:am|pm)?)$/i
|
8
|
+
return true unless condition =~ time_rx
|
9
|
+
|
10
|
+
now = Journal.date
|
11
|
+
m = condition.match(time_rx)
|
12
|
+
time = Chronic.parse(m['time'])
|
13
|
+
Journal.notify("{br}Invalid time string in question (#{m['time']})", exit_code: 4) unless time
|
14
|
+
|
15
|
+
case m['comp']
|
16
|
+
when /^<=$/
|
17
|
+
now <= time
|
18
|
+
when /^(<|bef)/i
|
19
|
+
now < time
|
20
|
+
when /^>=/
|
21
|
+
now >= time
|
22
|
+
when /^(>|aft)/i
|
23
|
+
now > time
|
24
|
+
end
|
25
|
+
# TODO: Other condition types
|
26
|
+
end
|
27
|
+
end
|
data/lib/journal-cli/version.rb
CHANGED
data/lib/journal-cli/weather.rb
CHANGED
@@ -54,6 +54,14 @@ module Journal
|
|
54
54
|
}
|
55
55
|
end
|
56
56
|
|
57
|
+
def current
|
58
|
+
"#{@data[:temp]} and #{@data[:current_condition]}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def forecast
|
62
|
+
"#{@data[:condition]} #{@data[:high]}/#{@data[:low]}"
|
63
|
+
end
|
64
|
+
|
57
65
|
def to_s
|
58
66
|
"#{@data[:temp].round} and #{@data[:current_condition]} (#{@data[:high].round}/#{@data[:low].round})"
|
59
67
|
end
|
@@ -61,8 +69,8 @@ module Journal
|
|
61
69
|
def to_markdown
|
62
70
|
output = []
|
63
71
|
|
64
|
-
output << "Forecast for #{@data[:day]}: #{
|
65
|
-
output << "Currently: #{
|
72
|
+
output << "Forecast for #{@data[:day]}: #{forecast} "
|
73
|
+
output << "Currently: #{current}"
|
66
74
|
output << ''
|
67
75
|
|
68
76
|
# Hours
|
data/lib/journal-cli.rb
CHANGED
@@ -11,6 +11,7 @@ require 'tty-which'
|
|
11
11
|
require 'tty-reader'
|
12
12
|
require_relative 'journal-cli/version'
|
13
13
|
require_relative 'journal-cli/color'
|
14
|
+
require_relative 'journal-cli/string'
|
14
15
|
require_relative 'journal-cli/data'
|
15
16
|
require_relative 'journal-cli/weather'
|
16
17
|
require_relative 'journal-cli/checkin'
|
@@ -21,6 +22,8 @@ require_relative 'journal-cli/question'
|
|
21
22
|
# Main Journal module
|
22
23
|
module Journal
|
23
24
|
class << self
|
25
|
+
attr_accessor :date
|
26
|
+
|
24
27
|
def notify(string, debug: false, exit_code: nil)
|
25
28
|
if debug
|
26
29
|
$stderr.puts "{dw}#{string}{x}".x
|
data/src/_README.md
CHANGED
@@ -55,6 +55,10 @@ This file contains a YAML definition of your journal. Each journal gets a top-le
|
|
55
55
|
|
56
56
|
You can include weather data automatically by setting a question type to 'weather'. In order for this to work, you'll need to define `zip` and `weather_api` keys. `zip` is just your zip code, and `weather_api` is a key from WeatherAPI.com. Sign up [here](https://www.weatherapi.com/) for a free plan, and then visit the [profile page](https://www.weatherapi.com/my/) to see your API key at the top.
|
57
57
|
|
58
|
+
If a question type is set to `weather.forecast`, only the predicted condition, high, and low will be included in the JSON data for the question. A full printout of hourly temps will be included in the Markdown/Day One output.
|
59
|
+
|
60
|
+
If the question type is `weather.current`, only the current condition and temperature will be recorded to the JSON, and a string containing "[TEMP] and [CONDITION]" (e.g. "64 and Sunny") will be recorded to Markdown/Day One for the question.
|
61
|
+
|
58
62
|
### Journal Configuration
|
59
63
|
|
60
64
|
Edit the file at `~/.config/journal/journals.yaml` following this structure:
|
@@ -99,9 +103,12 @@ journals: # required key
|
|
99
103
|
- title: Weather # Title of the section (will create template sections in Day One)
|
100
104
|
key: weather # the key to use in the structured data, will contain all of the answers
|
101
105
|
questions: # required key
|
102
|
-
- prompt: Current
|
106
|
+
- prompt: Current Weather
|
107
|
+
key: weather.current
|
108
|
+
type: weather.current
|
109
|
+
- prompt: Weather Forecast # The prompt shown on the command line, will also become a header in the journal entries (Markdown, Day One)
|
103
110
|
key: weather.forecast # if a key contains a dot, it will create nested data, e.g. `{ 'weather': { 'forecast': data } }`
|
104
|
-
type: weather # Set this to weather for weather data
|
111
|
+
type: weather.forecast # Set this to weather for weather data
|
105
112
|
- title: Health # New section
|
106
113
|
key: health
|
107
114
|
questions:
|
@@ -144,10 +151,18 @@ A question `type` can be one of:
|
|
144
151
|
- `text` or `string` will request a single-line string, submitted on return
|
145
152
|
- `multiline` for multiline strings (opens a readline editor, use ctrl-d to save)
|
146
153
|
- `weather` will just insert current weather data with no prompt
|
154
|
+
* `weather.forecast` will insert just the forecast
|
155
|
+
* `weather.current` will insert just the current temperature and condition
|
147
156
|
- `number` or `float` will request numeric input, stored as a float (decimal)
|
148
157
|
- `integer` will convert numeric input to the nearest integer
|
149
158
|
- `date` will request a natural language date which will be parsed into a date object
|
150
159
|
|
160
|
+
### Conditional Questions
|
161
|
+
|
162
|
+
You can have a question only show up based on conditions. Currently the only condition is time based. Just add a key called `condition` to the question definition, then include a natural language string like `before noon` or `after 3pm`. If the condition is matched, then the question will be displayed, otherwise it will be skipped and its data entry in the JSON will be null.
|
163
|
+
|
164
|
+
Conditions can be applied to individual questions, or to entire sections, depending on where the `condition` key is placed.
|
165
|
+
|
151
166
|
### Naming Keys
|
152
167
|
|
153
168
|
If you want data stored in a nested object, you can set a question type to `dictionary` and set the prompt to `null` (or just leave the key out), but give it a key that will serve as the parent in the object. Then in the nested questions, give them a key in the dot format `[PARENT_KEY].[CHILD_KEY]`. Section keys automatically nest their questions, but if you want to go deeper, you could have a question with the key `health` and type `dictionary`, then have questions with keys like `health.rating` and `health.notes`. If the section key was `status`, the resulting dictionary would look like this in the JSON:
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: journal-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.24
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tty-which
|
@@ -238,6 +238,7 @@ files:
|
|
238
238
|
- lib/journal-cli/question.rb
|
239
239
|
- lib/journal-cli/section.rb
|
240
240
|
- lib/journal-cli/sections.rb
|
241
|
+
- lib/journal-cli/string.rb
|
241
242
|
- lib/journal-cli/version.rb
|
242
243
|
- lib/journal-cli/weather.rb
|
243
244
|
- src/_README.md
|