friends 0.34 → 0.35
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +79 -20
- data/RELEASING.md +7 -6
- data/friends.md +4 -0
- data/lib/friends/activity.rb +4 -291
- data/lib/friends/commands/add.rb +15 -13
- data/lib/friends/commands/list.rb +51 -47
- data/lib/friends/commands/stats.rb +3 -0
- data/lib/friends/event.rb +298 -0
- data/lib/friends/introvert.rb +109 -57
- data/lib/friends/note.rb +19 -0
- data/lib/friends/version.rb +1 -1
- data/test/add_event_helper.rb +410 -0
- data/test/commands/add/activity_spec.rb +6 -350
- data/test/commands/add/note_spec.rb +55 -0
- data/test/commands/clean_spec.rb +25 -3
- data/test/commands/list/notes_spec.rb +179 -0
- data/test/commands/list/tags_spec.rb +24 -0
- data/test/commands/stats_spec.rb +100 -7
- data/test/helper.rb +12 -0
- metadata +10 -2
@@ -3,323 +3,7 @@
|
|
3
3
|
require "date"
|
4
4
|
|
5
5
|
require "./test/helper"
|
6
|
-
|
7
|
-
def date_parsing_specs(test_stdout: true)
|
8
|
-
describe "date parsing" do
|
9
|
-
let(:description) { "Test." }
|
10
|
-
|
11
|
-
describe "when date is in YYYY-MM-DD" do
|
12
|
-
let(:date) { "2017-01-01" }
|
13
|
-
|
14
|
-
it { line_added "- #{date}: #{description}" }
|
15
|
-
it { stdout_only "Activity added: \"#{date}: #{description}\"" } if test_stdout
|
16
|
-
end
|
17
|
-
|
18
|
-
describe "when date is in MM-DD-YYYY" do
|
19
|
-
let(:date) { "01-02-2017" }
|
20
|
-
|
21
|
-
it { line_added "- 2017-01-02: #{description}" }
|
22
|
-
it { stdout_only "Activity added: \"2017-01-02: #{description}\"" } if test_stdout
|
23
|
-
end
|
24
|
-
|
25
|
-
describe "when date is invalid" do
|
26
|
-
let(:date) { "2017-02-30" }
|
27
|
-
|
28
|
-
it { line_added "- 2017-03-02: #{description}" }
|
29
|
-
it { stdout_only "Activity added: \"2017-03-02: #{description}\"" } if test_stdout
|
30
|
-
end
|
31
|
-
|
32
|
-
describe "when date is natural language and in full" do
|
33
|
-
let(:date) { "February 23rd, 2017" }
|
34
|
-
|
35
|
-
it { line_added "- 2017-02-23: #{description}" }
|
36
|
-
it { stdout_only "Activity added: \"2017-02-23: #{description}\"" } if test_stdout
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "when date is natural language and only month and day" do
|
40
|
-
# We use two days rather than just one to avoid strange behavior around
|
41
|
-
# edge cases when the test is being run right around midnight.
|
42
|
-
let(:two_days_in_seconds) { 2 * 24 * 60 * 60 }
|
43
|
-
let(:raw_date) { Time.now + two_days_in_seconds }
|
44
|
-
let(:date) { raw_date.strftime("%B %d") }
|
45
|
-
let(:expected_year) { raw_date.strftime("%Y").to_i - 1 }
|
46
|
-
let(:expected_date_str) { "#{expected_year}-#{raw_date.strftime('%m-%d')}" }
|
47
|
-
|
48
|
-
it { line_added "- #{expected_date_str}: #{description}" }
|
49
|
-
it { stdout_only "Activity added: \"#{expected_date_str}: #{description}\"" } if test_stdout
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def description_parsing_specs(test_stdout: true)
|
55
|
-
describe "description parsing" do
|
56
|
-
let(:date) { Date.today.strftime }
|
57
|
-
|
58
|
-
describe "when description includes a friend's full name (case insensitive)" do
|
59
|
-
let(:description) { "Lunch with grace hopper." }
|
60
|
-
|
61
|
-
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
|
62
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with Grace Hopper.\"" } if test_stdout
|
63
|
-
end
|
64
|
-
|
65
|
-
describe "when description includes a friend's first name (case insensitive)" do
|
66
|
-
let(:description) { "Lunch with grace." }
|
67
|
-
|
68
|
-
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
|
69
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with Grace Hopper.\"" } if test_stdout
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "when description has a friend's first name and last initial (case insensitive)" do
|
73
|
-
describe "when followed by no period" do
|
74
|
-
let(:description) { "Lunch with grace h" }
|
75
|
-
|
76
|
-
it { line_added "- #{date}: Lunch with **Grace Hopper**" }
|
77
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with Grace Hopper\"" } if test_stdout
|
78
|
-
end
|
79
|
-
|
80
|
-
describe "when followed by a period at the end of a sentence" do
|
81
|
-
let(:description) { "Met grace h. So fun!" }
|
82
|
-
|
83
|
-
it { line_added "- #{date}: Met **Grace Hopper**. So fun!" }
|
84
|
-
it { stdout_only "Activity added: \"#{date}: Met Grace Hopper. So fun!\"" } if test_stdout
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "when followed by a period at the end of the description" do
|
88
|
-
let(:description) { "Lunch with grace h." }
|
89
|
-
|
90
|
-
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
|
91
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with Grace Hopper.\"" } if test_stdout
|
92
|
-
end
|
93
|
-
|
94
|
-
describe "when followed by a period in the middle of a sentence" do
|
95
|
-
let(:description) { "Met grace h. at 12." }
|
96
|
-
|
97
|
-
it { line_added "- #{date}: Met **Grace Hopper** at 12." }
|
98
|
-
it { stdout_only "Activity added: \"#{date}: Met Grace Hopper at 12.\"" } if test_stdout
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
describe "when description includes a friend's nickname (case insensitive)" do
|
103
|
-
let(:description) { "Lunch with the admiral." }
|
104
|
-
|
105
|
-
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
|
106
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with Grace Hopper.\"" } if test_stdout
|
107
|
-
end
|
108
|
-
|
109
|
-
describe "when description includes a friend's nickname which contains a name" do
|
110
|
-
let(:description) { "Lunch with Amazing Grace." }
|
111
|
-
|
112
|
-
it { line_added "- #{date}: Lunch with **Grace Hopper**." }
|
113
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with Grace Hopper.\"" } if test_stdout
|
114
|
-
end
|
115
|
-
|
116
|
-
describe "when description includes a friend's name at the beginning of a word" do
|
117
|
-
# Capitalization reduces chance of a false positive.
|
118
|
-
let(:description) { "Gracefully strolled." }
|
119
|
-
|
120
|
-
it { line_added "- #{date}: Gracefully strolled." }
|
121
|
-
it { stdout_only "Activity added: \"#{date}: Gracefully strolled.\"" } if test_stdout
|
122
|
-
end
|
123
|
-
|
124
|
-
describe "when description includes a friend's name at the end of a word" do
|
125
|
-
# Capitalization reduces chance of a false positive.
|
126
|
-
let(:description) { "The service was a disGrace." }
|
127
|
-
|
128
|
-
it { line_added "- #{date}: The service was a disGrace." }
|
129
|
-
it { stdout_only "Activity added: \"#{date}: The service was a disGrace.\"" } if test_stdout
|
130
|
-
end
|
131
|
-
|
132
|
-
describe "when description includes a friend's name in the middle of a word" do
|
133
|
-
# Capitalization reduces chance of a false positive.
|
134
|
-
let(:description) { "The service was disGraceful." }
|
135
|
-
|
136
|
-
it { line_added "- #{date}: The service was disGraceful." }
|
137
|
-
it { stdout_only "Activity added: \"#{date}: The service was disGraceful.\"" } if test_stdout
|
138
|
-
end
|
139
|
-
|
140
|
-
describe "when a friend's name is escaped with a backslash" do
|
141
|
-
# We have to use four backslashes here because of Ruby's backslash escaping; when this
|
142
|
-
# goes through all of the layers of this test it emerges on the other side as a single one.
|
143
|
-
let(:description) { "Dinner with \\\\Grace Kelly." }
|
144
|
-
|
145
|
-
it { line_added "- #{date}: Dinner with Grace Kelly." }
|
146
|
-
it { stdout_only "Activity added: \"#{date}: Dinner with Grace Kelly.\"" } if test_stdout
|
147
|
-
end
|
148
|
-
|
149
|
-
describe "hyphenated name edge cases" do
|
150
|
-
describe "when one name precedes another before a hyphen" do
|
151
|
-
let(:description) { "Shopped w/ Mary-Kate." }
|
152
|
-
|
153
|
-
# Make sure "Mary" is a closer friend than "Mary-Kate" so we know our
|
154
|
-
# test result isn't due to chance.
|
155
|
-
let(:content) do
|
156
|
-
<<-FILE
|
157
|
-
### Activities:
|
158
|
-
- 2017-01-01: Singing with **Mary Poppins**.
|
159
|
-
|
160
|
-
### Friends:
|
161
|
-
- Mary Poppins
|
162
|
-
- Mary-Kate Olsen
|
163
|
-
|
164
|
-
### Locations:
|
165
|
-
FILE
|
166
|
-
end
|
167
|
-
|
168
|
-
it { line_added "- #{date}: Shopped w/ **Mary-Kate Olsen**." }
|
169
|
-
it { stdout_only "Activity added: \"#{date}: Shopped w/ Mary-Kate Olsen.\"" } if test_stdout
|
170
|
-
end
|
171
|
-
|
172
|
-
describe "when one name follows another after a hyphen" do
|
173
|
-
let(:description) { "Shopped w/ Mary-Kate." }
|
174
|
-
|
175
|
-
# Make sure "Kate" is a closer friend than "Mary-Kate" so we know our
|
176
|
-
# test result isn't due to chance.
|
177
|
-
let(:content) do
|
178
|
-
<<-FILE
|
179
|
-
### Activities:
|
180
|
-
- 2017-01-01: Improv with **Kate Winslet**.
|
181
|
-
|
182
|
-
### Friends:
|
183
|
-
- Kate Winslet
|
184
|
-
- Mary-Kate Olsen
|
185
|
-
|
186
|
-
### Locations:
|
187
|
-
FILE
|
188
|
-
end
|
189
|
-
|
190
|
-
it { line_added "- #{date}: Shopped w/ **Mary-Kate Olsen**." }
|
191
|
-
it { stdout_only "Activity added: \"#{date}: Shopped w/ Mary-Kate Olsen.\"" } if test_stdout
|
192
|
-
end
|
193
|
-
|
194
|
-
describe "when one name is contained within another via hyphens" do
|
195
|
-
let(:description) { "Met Mary-Jo-Kate." }
|
196
|
-
|
197
|
-
# Make sure "Jo" is a closer friend than "Mary-Jo-Kate" so we know our
|
198
|
-
# test result isn't due to chance.
|
199
|
-
let(:content) do
|
200
|
-
<<-FILE
|
201
|
-
### Activities:
|
202
|
-
- 2017-01-01: Singing with **Jo Stafford**.
|
203
|
-
|
204
|
-
### Friends:
|
205
|
-
- Jo Stafford
|
206
|
-
- Mary-Jo-Kate Olsen
|
207
|
-
|
208
|
-
### Locations:
|
209
|
-
FILE
|
210
|
-
end
|
211
|
-
|
212
|
-
it { line_added "- #{date}: Met **Mary-Jo-Kate Olsen**." }
|
213
|
-
it { stdout_only "Activity added: \"#{date}: Met Mary-Jo-Kate Olsen.\"" } if test_stdout
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
describe "when description has a friend's name with leading asterisks" do
|
218
|
-
let(:description) { "Lunch with **Grace Hopper." }
|
219
|
-
|
220
|
-
it { line_added "- #{date}: Lunch with **Grace Hopper." }
|
221
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with **Grace Hopper.\"" } if test_stdout
|
222
|
-
end
|
223
|
-
|
224
|
-
describe "when description has a friend's name with trailing asterisks" do
|
225
|
-
# Note: We can't guarantee that "Grace Hopper**" doesn't match because the "Grace" isn't
|
226
|
-
# surrounded by asterisks.
|
227
|
-
let(:description) { "Lunch with Grace**." }
|
228
|
-
|
229
|
-
it { line_added "- #{date}: Lunch with Grace**." }
|
230
|
-
it { stdout_only "Activity added: \"#{date}: Lunch with Grace**.\"" } if test_stdout
|
231
|
-
end
|
232
|
-
|
233
|
-
describe "when description has a friend's name multiple times" do
|
234
|
-
let(:description) { "Grace! Grace!!!" }
|
235
|
-
|
236
|
-
it { line_added "- #{date}: **Grace Hopper**! **Grace Hopper**!!!" }
|
237
|
-
it { stdout_only "Activity added: \"#{date}: Grace Hopper! Grace Hopper!!!\"" } if test_stdout
|
238
|
-
end
|
239
|
-
|
240
|
-
describe "when description has a name with multiple friend matches" do
|
241
|
-
describe "when there is useful context from past activities" do
|
242
|
-
let(:description) { "Met John + Elizabeth." }
|
243
|
-
|
244
|
-
# Create a past activity in which Elizabeth Cady Stanton did something
|
245
|
-
# with John Cage. Then, create past activities to make Elizabeth II a
|
246
|
-
# better friend than Elizabeth Cady Stanton.
|
247
|
-
let(:content) do
|
248
|
-
<<-FILE
|
249
|
-
### Activities:
|
250
|
-
- 2017-01-05: Picnic with **Elizabeth Cady Stanton** and **John Cage**.
|
251
|
-
- 2017-01-04: Got lunch with **Elizabeth II**.
|
252
|
-
- 2017-01-03: Ice skated with **Elizabeth II**.
|
253
|
-
|
254
|
-
### Friends:
|
255
|
-
- Elizabeth Cady Stanton
|
256
|
-
- Elizabeth II
|
257
|
-
- John Cage
|
258
|
-
|
259
|
-
### Locations:
|
260
|
-
FILE
|
261
|
-
end
|
262
|
-
|
263
|
-
# Elizabeth II is the better friend, but historical activities have
|
264
|
-
# had Elizabeth Cady Stanton and John Cage together. Thus, we should
|
265
|
-
# interpret "Elizabeth" as Elizabeth Cady Stanton.
|
266
|
-
it { line_added "- #{date}: Met **John Cage** + **Elizabeth Cady Stanton**." }
|
267
|
-
if test_stdout
|
268
|
-
it { stdout_only "Activity added: \"#{date}: Met John Cage + Elizabeth Cady Stanton.\"" }
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
describe "when there is no useful context from past activities" do
|
273
|
-
let(:description) { "Dinner with John and Elizabeth." }
|
274
|
-
|
275
|
-
# Create a past activity in which Elizabeth Cady Stanton did something
|
276
|
-
# with John Cage. Then, create past activities to make Elizabeth II a
|
277
|
-
# better friend than Elizabeth Cady Stanton.
|
278
|
-
let(:content) do
|
279
|
-
<<-FILE
|
280
|
-
### Activities:
|
281
|
-
- 2017-01-03: Ice skated with **Elizabeth II**.
|
282
|
-
|
283
|
-
### Friends:
|
284
|
-
- Elizabeth Cady Stanton
|
285
|
-
- Elizabeth II
|
286
|
-
- John Cage
|
287
|
-
|
288
|
-
### Locations:
|
289
|
-
FILE
|
290
|
-
end
|
291
|
-
|
292
|
-
# Pick the "Elizabeth" with more activities.
|
293
|
-
it { line_added "- #{date}: Dinner with **John Cage** and **Elizabeth II**." }
|
294
|
-
if test_stdout
|
295
|
-
it { stdout_only "Activity added: \"#{date}: Dinner with John Cage and Elizabeth II.\"" }
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
describe "when description contains a location name (case insensitive)" do
|
301
|
-
let(:description) { "Lunch at a cafe in paris." }
|
302
|
-
|
303
|
-
it { line_added "- #{date}: Lunch at a cafe in _Paris_." }
|
304
|
-
it { stdout_only "Activity added: \"#{date}: Lunch at a cafe in Paris.\"" } if test_stdout
|
305
|
-
end
|
306
|
-
|
307
|
-
describe "when description contains both names and locations" do
|
308
|
-
let(:description) { "Grace and I went to Atlantis and then Paris for lunch with George." }
|
309
|
-
|
310
|
-
it do
|
311
|
-
line_added "- #{date}: **Grace Hopper** and I went to _Atlantis_ and then _Paris_ for "\
|
312
|
-
"lunch with **George Washington Carver**."
|
313
|
-
end
|
314
|
-
if test_stdout
|
315
|
-
it do
|
316
|
-
stdout_only "Activity added: \"#{date}: Grace Hopper and I went to Atlantis and then "\
|
317
|
-
"Paris for lunch with George Washington Carver.\""
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
6
|
+
require "./test/add_event_helper"
|
323
7
|
|
324
8
|
clean_describe "add activity" do
|
325
9
|
let(:content) { CONTENT }
|
@@ -332,6 +16,8 @@ clean_describe "add activity" do
|
|
332
16
|
- 2017-01-01: Activity 1.
|
333
17
|
- 2016-01-01: Activity one year earlier.
|
334
18
|
|
19
|
+
### Notes:
|
20
|
+
|
335
21
|
### Friends:
|
336
22
|
|
337
23
|
### Locations:
|
@@ -356,6 +42,8 @@ FILE
|
|
356
42
|
- 2017-01-01: Activity 1.
|
357
43
|
- 2016-01-01: Activity one year earlier.
|
358
44
|
|
45
|
+
### Notes:
|
46
|
+
|
359
47
|
### Friends:
|
360
48
|
|
361
49
|
### Locations:
|
@@ -363,37 +51,5 @@ FILE
|
|
363
51
|
end
|
364
52
|
end
|
365
53
|
|
366
|
-
|
367
|
-
subject { run_cmd("add activity #{date}: #{description}") }
|
368
|
-
|
369
|
-
date_parsing_specs
|
370
|
-
description_parsing_specs
|
371
|
-
end
|
372
|
-
|
373
|
-
describe "when given only a date in the command" do
|
374
|
-
subject { run_cmd("add activity #{date}", stdin_data: description) }
|
375
|
-
|
376
|
-
# We don't try to test the STDOUT here because our command prompt produces other STDOUT that's
|
377
|
-
# hard to test.
|
378
|
-
date_parsing_specs(test_stdout: false)
|
379
|
-
description_parsing_specs(test_stdout: false)
|
380
|
-
end
|
381
|
-
|
382
|
-
describe "when given only a description in the command" do
|
383
|
-
subject { run_cmd("add activity #{description}") }
|
384
|
-
|
385
|
-
# We don't test date parsing since in this case the date is always inferred to be today.
|
386
|
-
|
387
|
-
description_parsing_specs
|
388
|
-
end
|
389
|
-
|
390
|
-
describe "when given neither a date nor a description in the command" do
|
391
|
-
subject { run_cmd("add activity", stdin_data: description) }
|
392
|
-
|
393
|
-
# We don't test date parsing since in this case the date is always inferred to be today.
|
394
|
-
|
395
|
-
# We don't try to test the STDOUT here because our command prompt produces other STDOUT that's
|
396
|
-
# hard to test.
|
397
|
-
description_parsing_specs(test_stdout: false)
|
398
|
-
end
|
54
|
+
parsing_specs(event: :activity)
|
399
55
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
require "./test/helper"
|
6
|
+
require "./test/add_event_helper"
|
7
|
+
|
8
|
+
clean_describe "add note" do
|
9
|
+
let(:content) { CONTENT }
|
10
|
+
|
11
|
+
describe "date ordering" do
|
12
|
+
let(:content) do
|
13
|
+
<<-FILE
|
14
|
+
### Activities:
|
15
|
+
|
16
|
+
### Notes:
|
17
|
+
- 2018-01-01: Note one year later.
|
18
|
+
- 2017-01-01: Note 1.
|
19
|
+
- 2016-01-01: Note one year earlier.
|
20
|
+
|
21
|
+
### Friends:
|
22
|
+
|
23
|
+
### Locations:
|
24
|
+
FILE
|
25
|
+
end
|
26
|
+
|
27
|
+
subject do
|
28
|
+
4.times do |i|
|
29
|
+
run_cmd("add note 2017-01-01: Note #{i + 2}.")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "orders dates by insertion time" do
|
34
|
+
subject
|
35
|
+
File.read(filename).must_equal <<-FILE
|
36
|
+
### Activities:
|
37
|
+
|
38
|
+
### Notes:
|
39
|
+
- 2018-01-01: Note one year later.
|
40
|
+
- 2017-01-01: Note 5.
|
41
|
+
- 2017-01-01: Note 4.
|
42
|
+
- 2017-01-01: Note 3.
|
43
|
+
- 2017-01-01: Note 2.
|
44
|
+
- 2017-01-01: Note 1.
|
45
|
+
- 2016-01-01: Note one year earlier.
|
46
|
+
|
47
|
+
### Friends:
|
48
|
+
|
49
|
+
### Locations:
|
50
|
+
FILE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
parsing_specs(event: :note)
|
55
|
+
end
|
data/test/commands/clean_spec.rb
CHANGED
@@ -25,6 +25,8 @@ clean_describe "clean" do
|
|
25
25
|
file_equals <<-FILE
|
26
26
|
### Activities:
|
27
27
|
|
28
|
+
### Notes:
|
29
|
+
|
28
30
|
### Friends:
|
29
31
|
|
30
32
|
### Locations:
|
@@ -33,10 +35,30 @@ clean_describe "clean" do
|
|
33
35
|
end
|
34
36
|
|
35
37
|
describe "when file has content" do
|
36
|
-
|
38
|
+
describe "when content is formatted correctly" do
|
39
|
+
let(:content) { SCRAMBLED_CONTENT }
|
40
|
+
|
41
|
+
it "writes the file with contents sorted" do
|
42
|
+
file_equals CONTENT
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "when a header is malformed" do
|
47
|
+
let(:content) do
|
48
|
+
<<-FILE
|
49
|
+
### Activities:
|
50
|
+
|
51
|
+
### Garbage:
|
52
|
+
|
53
|
+
### Friends:
|
54
|
+
|
55
|
+
### Locations:
|
56
|
+
FILE
|
57
|
+
end
|
37
58
|
|
38
|
-
|
39
|
-
|
59
|
+
it "prints an error message" do
|
60
|
+
stderr_only 'Error: Expected "a valid header" on line 3'
|
61
|
+
end
|
40
62
|
end
|
41
63
|
end
|
42
64
|
end
|