standup_md 0.1.2 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile.lock +7 -1
  4. data/README.md +183 -97
  5. data/Rakefile +11 -28
  6. data/bin/standup +1 -1
  7. data/doc/README_md.html +185 -78
  8. data/doc/StandupMD.html +83 -1244
  9. data/doc/StandupMD/Cli.html +124 -474
  10. data/doc/StandupMD/Cli/Helpers.html +167 -0
  11. data/doc/StandupMD/Config.html +230 -0
  12. data/doc/StandupMD/Config/Cli.html +355 -0
  13. data/doc/StandupMD/Config/Entry.html +284 -0
  14. data/doc/StandupMD/Config/EntryList.html +197 -0
  15. data/doc/StandupMD/Config/File.html +609 -0
  16. data/doc/StandupMD/Entry.html +478 -0
  17. data/doc/StandupMD/EntryList.html +759 -0
  18. data/doc/StandupMD/File.html +575 -0
  19. data/doc/created.rid +15 -8
  20. data/doc/index.html +191 -79
  21. data/doc/js/navigation.js.gz +0 -0
  22. data/doc/js/search_index.js +1 -1
  23. data/doc/js/search_index.js.gz +0 -0
  24. data/doc/js/searcher.js.gz +0 -0
  25. data/doc/table_of_contents.html +150 -265
  26. data/lib/standup_md.rb +29 -508
  27. data/lib/standup_md/cli.rb +63 -242
  28. data/lib/standup_md/cli/helpers.rb +165 -0
  29. data/lib/standup_md/config.rb +45 -0
  30. data/lib/standup_md/config/cli.rb +106 -0
  31. data/lib/standup_md/config/entry.rb +61 -0
  32. data/lib/standup_md/config/entry_list.rb +26 -0
  33. data/lib/standup_md/config/file.rb +199 -0
  34. data/lib/standup_md/entry.rb +121 -0
  35. data/lib/standup_md/entry_list.rb +166 -0
  36. data/lib/standup_md/file.rb +173 -0
  37. data/lib/standup_md/file/helpers.rb +62 -0
  38. data/lib/standup_md/version.rb +5 -3
  39. data/standup_md.gemspec +1 -0
  40. metadata +35 -5
  41. data/doc/TestCli.html +0 -792
  42. data/doc/TestHelper.html +0 -282
  43. data/doc/TestStandupMD.html +0 -1354
@@ -1,537 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'fileutils'
5
3
  require_relative 'standup_md/version'
4
+ require_relative 'standup_md/file'
5
+ require_relative 'standup_md/entry'
6
+ require_relative 'standup_md/entry_list'
7
+ require_relative 'standup_md/cli'
8
+ require_relative 'standup_md/config'
6
9
 
7
10
  ##
8
11
  # The class for handing reading/writing of entries.
9
- #
10
- # @example
11
- # su = StandupMD.new
12
- class StandupMD
12
+ module StandupMD
13
+ @config_file_loaded = false
13
14
 
14
15
  ##
15
- # Convenience method for calling +new+ + +load+
16
+ # Shorthand for +StanupMD::Cli+
16
17
  #
17
- # @param [Hash] attributes Attributes to set before loading.
18
- #
19
- # @example
20
- # su = StandupMD.load(bullet_character: '*')
21
- def self.load(attributes = {})
22
- self.new do |s|
23
- attributes.each do |k, v|
24
- next unless s.respond_to?(k)
25
- s.send("#{k}=", v)
26
- end
27
- end.load
18
+ # @return [StanupMD::Cli]
19
+ def self.config
20
+ @config ||= StandupMD::Config.new
28
21
  end
29
22
 
30
- # :section: Attributes that aren't settable by user, but are gettable.
31
-
32
- ##
33
- # The string that will be used for the entry headers.
34
- #
35
- # @return [String]
36
- attr_reader :header
37
-
38
- ##
39
- # The file name should equal file_name_format parsed by Date.strftime.
40
- # The default is +Date.today.strftime('%Y_%m.md')+
41
- #
42
- # @return [String]
43
- #
44
- # @example
45
- # su = StandupMD.new { |s| s.file_name_format = '%y_%m.markdown' }
46
- # su.file
47
- # # => Users/johnsmith/.cache/standup_md/20_04.markdown
48
- attr_reader :file
49
-
50
- ##
51
- # The file that contains previous entries. When last month's file exists, but
52
- # this month's doesn't or is empty, previous_file should equal last month's
53
- # file.
54
- #
55
- # @return [String]
56
- #
57
- # @example
58
- # # Assuming the current month is April, 2020
59
- #
60
- # Dir.entries(su.directory)
61
- # # => []
62
- # su = StandupMD.new
63
- # su.previous_file
64
- # # => ''
65
- #
66
- # Dir.entries(su.directory)
67
- # # => ['2020_03.md']
68
- # su = StandupMD.new
69
- # su.previous_file
70
- # # => '2020_03.md'
71
- #
72
- # Dir.entries(su.directory)
73
- # # => ['2020_03.md', '2020_04.md']
74
- # su = StandupMD.new
75
- # su.previous_file
76
- # # => '2020_04.md'
77
- attr_reader :previous_file
78
-
79
- ##
80
- # The entry for today's date as a hash. If +file+ already has an entry for
81
- # today, it will be read and used as +current_entry+. If there is no entry
82
- # for today, one should be generated from scaffolding.
83
- #
84
- # @return [Hash]
85
- #
86
- # @example
87
- # StandupMD.new.current_entry
88
- # # => {
89
- # # '2020-04-02' => {
90
- # # 'Previous' => ['Task from yesterday'],
91
- # # 'Current' => ["<!-- ADD TODAY'S WORK HERE -->"],
92
- # # 'Impediments' => ['None'],
93
- # # 'Notes' => [],
94
- # # }
95
- # # }
96
- attr_reader :current_entry
97
-
98
- ##
99
- # All previous entry for the same month as today. If it's the first day of
100
- # the month, +all_previous_entries+ will be all of last month's entries. They
101
- # will be a hash in the same format as +current_entry+.
102
- #
103
- # @return [Hash]
104
- attr_reader :all_previous_entries
105
-
106
- ##
107
- # Current entry plus all previous entries. This will be a hash in the same
108
- # format at +current_entry+ and +all_previous_entries+.
109
- #
110
- # @return [Hash]
111
- attr_reader :all_entries
112
-
113
- # :section: Attributes that are settable by the user, but have custom setters.
114
-
115
- ##
116
- # The directory where the markdown files are kept.
117
- #
118
- # @return [String]
119
- #
120
- # @default
121
- # File.join(ENV['HOME'], '.cache', 'standup_md')
122
- attr_reader :directory
123
-
124
- ##
125
- # Array of tasks for today. This is the work expected to be performed today.
126
- # Default is an empty array, but when writing to file, the default is
127
- #
128
- # @return [Array]
129
- #
130
- # @default
131
- # ["<!-- ADD TODAY'S WORK HERE -->"]
132
- attr_reader :current_entry_tasks
133
-
134
- ##
135
- # Array of impediments for today's entry.
136
- #
137
- # @return [Array]
138
- attr_reader :impediments
139
-
140
23
  ##
141
- # Character used as bullets for list entries.
142
- #
143
- # @return [String] either - (dash) or * (asterisk)
144
- attr_reader :bullet_character
145
-
146
- ##
147
- # Number of octothorps that should preface entry headers.
148
- #
149
- # @return [Integer] between 1 and 5
150
- attr_reader :header_depth
151
-
152
- ##
153
- # Number of octothorps that should preface sub-headers.
154
- #
155
- # @return [Integer] between 2 and 6
156
- attr_reader :sub_header_depth
157
-
158
- ##
159
- # The tasks from the previous task's "Current" section.
160
- #
161
- # @return [Array]
162
- attr_reader :previous_entry_tasks
163
-
164
- ##
165
- # Array of notes to add to today's entry.
166
- #
167
- # @return [Array]
168
- attr_reader :notes
169
-
170
- # :section: Attributes with default getters and setters.
171
-
172
- ##
173
- # The format to use for file names. This should include a month (%m) and
174
- # year (%y) so the file can rotate every month. This will prevent files
175
- # from getting too large.
176
- #
177
- # @param [String] file_name_format Parsed by +strftime+
178
- #
179
- # @return [String]
180
- attr_accessor :file_name_format
181
-
182
- ##
183
- # The date format to use for entry headers.
184
- #
185
- # @param [String] header_date_format Parsed by +strftime+
186
- #
187
- # @return [String]
188
- attr_accessor :header_date_format
189
-
190
- ##
191
- # The header to use for the +Current+ section.
192
- #
193
- # @param [String] current_header
194
- #
195
- # @return [String]
196
- attr_accessor :current_header
197
-
198
- ##
199
- # The header to use for the +Previous+ section.
200
- #
201
- # @param [String] previous_header
202
- #
203
- # @return [String]
204
- attr_accessor :previous_header
205
-
206
- ##
207
- # The header to use for the +Impediments+ section.
208
- #
209
- # @param [String] impediments_header
210
- #
211
- # @return [String]
212
- attr_accessor :impediments_header
213
-
214
- ##
215
- # The header to use for the +Notes+ section.
216
- #
217
- # @param [String] notes_header
218
- #
219
- # @return [String]
220
- attr_accessor :notes_header
221
-
222
- ##
223
- # Constructor. Yields the instance so you can pass a block to access setters.
224
- #
225
- # @return [self]
24
+ # Reset all configuration values to their defaults.
226
25
  #
227
- # @example
228
- # su = StandupMD.new do |s|
229
- # s.directory = @workdir
230
- # s.file_name_format = '%y_%m.markdown'
231
- # s.bullet_character = '*'
232
- # end
233
- def initialize
234
- @notes = []
235
- @header_depth = 1
236
- @sub_header_depth = 2
237
- @bullet_character = '-'
238
- @current_entry_tasks = ["<!-- ADD TODAY'S WORK HERE -->"]
239
- @impediments = ['None']
240
- @file_name_format = '%Y_%m.md'
241
- @directory = File.join(ENV['HOME'], '.cache', 'standup_md')
242
- @header_date_format = '%Y-%m-%d'
243
- @current_header = 'Current'
244
- @previous_header = 'Previous'
245
- @impediments_header = 'Impediments'
246
- @notes_header = 'Notes'
247
- @sub_header_order = %w[previous current impediments notes]
248
-
249
- yield self if block_given?
26
+ # @return [StandupMD::Config]
27
+ def self.reset_config
28
+ @config = StandupMD::Config.new
250
29
  end
251
30
 
252
- # :section: Booleans
253
- # Helper methods for booleans.
254
-
255
31
  ##
256
- # Has the file been written since instantiated?
257
- #
258
- # @return [boolean]
32
+ # Allows for configuration via a block. Useful when making config files.
259
33
  #
260
34
  # @example
261
- # su = StandupMD.new
262
- # su.file_written?
263
- # # => false
264
- # su.write
265
- # su.file_written?
266
- # # => true
267
- def file_written?
268
- @file_written
269
- end
270
-
271
- ##
272
- # Was today's entry already in the file?
273
- #
274
- # @return [boolean] true if today's entry was already in the file
275
- def entry_previously_added?
276
- @entry_previously_added
277
- end
278
-
279
- # :section: Custom setters
280
- # Setters that required validations.
281
-
282
- ##
283
- # Setter for current entry tasks.
284
- #
285
- # @param [Array] tasks
286
- #
287
- # @return [Array]
288
- def previous_entry_tasks=(tasks)
289
- raise 'Must be an Array' unless tasks.is_a?(Array)
290
- @previous_entry_tasks = tasks
291
- end
292
-
293
- ##
294
- # Setter for notes.
295
- #
296
- # @param [Array] notes
297
- #
298
- # @return [Array]
299
- def notes=(tasks)
300
- raise 'Must be an Array' unless tasks.is_a?(Array)
301
- @notes = tasks
302
- end
303
-
304
- ##
305
- # Setter for current entry tasks.
306
- #
307
- # @param [Array] tasks
308
- #
309
- # @return [Array]
310
- def current_entry_tasks=(tasks)
311
- raise 'Must be an Array' unless tasks.is_a?(Array)
312
- @current_entry_tasks = tasks
35
+ # StandupMD.configure { |s| s.cli.editor = 'mate' }
36
+ def self.configure
37
+ yield self.config
313
38
  end
314
39
 
315
40
  ##
316
- # Setter for impediments.
317
- #
318
- # @param [Array] tasks
319
- #
320
- # @return [Array]
321
- def impediments=(tasks)
322
- raise 'Must be an Array' unless tasks.is_a?(Array)
323
- @impediments = tasks
324
- end
325
-
326
- ##
327
- # Setter for bullet_character. Must be * (asterisk) or - (dash).
328
- #
329
- # @param [String] character
330
- #
331
- # @return [String]
332
- def bullet_character=(character)
333
- raise 'Must be "-" or "*"' unless %w[- *].include?(character)
334
- @bullet_character = character
335
- end
336
-
337
- ##
338
- # Setter for directory. Must be expanded in case the user uses `~` for home.
339
- # If the directory doesn't exist, it will be created. To reset instance
340
- # variables after changing the directory, you'll need to call load.
341
- #
342
- # @param [String] directory
343
- #
344
- # @return [String]
345
- def directory=(directory)
346
- # TODO test this
347
- directory = File.expand_path(directory)
348
- FileUtils.mkdir_p(directory) unless File.directory?(directory)
349
- @directory = directory
350
- end
351
-
352
- ##
353
- # Number of octothorps (#) to use before the main header.
354
- #
355
- # @param [Integer] depth
356
- #
357
- # @return [Integer]
358
- def header_depth=(depth)
359
- if !depth.between?(1, 5)
360
- raise 'Header depth out of bounds (1..5)'
361
- elsif depth >= sub_header_depth
362
- raise 'header_depth must be larger than sub_header_depth'
363
- end
364
- @header_depth = depth
365
- end
366
-
367
- ##
368
- # Number of octothorps (#) to use before sub headers (Current, Previous, etc).
369
- #
370
- # @param [Integer] depth
371
- #
372
- # @return [Integer]
373
- def sub_header_depth=(depth)
374
- if !depth.between?(2, 6)
375
- raise 'Sub-header depth out of bounds (2..6)'
376
- elsif depth <= header_depth
377
- raise 'sub_header_depth must be smaller than header_depth'
378
- end
379
- @sub_header_depth = depth
380
- end
381
-
382
- ##
383
- # Preferred order for sub-headers.
384
- #
385
- # @param [Array] Values must be %w[previous current impediment notes]
386
- #
387
- # @return [Array]
388
- def sub_header_order=(array)
389
- order = %w[previous current impediments notes]
390
- raise "Values must be #{order.join{', '}}" unless order.sort == array.sort
391
- @sub_header_order = array
392
- end
393
-
394
- # :section: Misc
395
- # Misc.
396
-
397
- ##
398
- # Return a copy of the sub-header order so the user can't modify the array.
399
- #
400
- # @return [Array]
401
- def sub_header_order
402
- @sub_header_order.dup
403
- end
404
-
405
- ##
406
- # Writes a new entry to the file if the first entry in the file isn't today.
41
+ # Has a config file been loaded?
407
42
  #
408
43
  # @return [Boolean]
409
- def write
410
- File.open(file, 'w') do |f|
411
- all_entries.each do |head, s_heads|
412
- f.puts '#' * header_depth + ' ' + head
413
- sub_header_order.map { |value| "#{value}_header" }.each do |sub_head|
414
- sh = send(sub_head).capitalize
415
- next if !s_heads[sh] || s_heads[sh].empty?
416
- f.puts '#' * sub_header_depth + ' ' + sh
417
- s_heads[sh].each { |task| f.puts bullet_character + ' ' + task }
418
- end
419
- f.puts
420
- break if new_month?
421
- end
422
- end
423
- @file_written = true
44
+ def self.config_file_loaded?
45
+ @config_file_loaded
424
46
  end
425
47
 
426
48
  ##
427
- # Sets internal instance variables. Called when first instantiated, or after
428
- # directory is set.
49
+ # Loads a config file.
429
50
  #
430
- # @return [self]
431
- def load
432
- FileUtils.mkdir_p(directory) unless File.directory?(directory)
433
-
434
- @today = Date.today
435
- @header = today.strftime(header_date_format)
436
- @file_written = false
437
- @file = File.expand_path(File.join(directory, today.strftime(file_name_format)))
438
- @previous_file = get_previous_file
439
- @all_previous_entries = get_all_previous_entries
440
- @entry_previously_added = all_previous_entries.key?(header)
441
- @previous_entry_tasks = previous_entry[current_header]
442
- @current_entry = @all_previous_entries.delete(header) || new_entry
443
- @all_entries = {header => current_entry}.merge(all_previous_entries)
444
-
445
- FileUtils.touch(file) unless File.file?(file)
446
- self
447
- end
448
-
449
- ##
450
- # Alias of +load+
451
- #
452
- # @return [self]
453
- alias_method :reload, :load
454
-
455
- ##
456
- # Is today a different month than the previous entry?
457
- def new_month?
458
- file != previous_file
459
- end
460
-
461
- private
462
-
463
- ##
464
- # Scaffolding with which new entries will be created.
465
- def new_entry # :nodoc:
466
- {
467
- previous_header => previous_entry_tasks || [],
468
- current_header => current_entry_tasks,
469
- impediments_header => impediments,
470
- notes_header => notes,
471
- }
472
- end
473
-
474
- ##
475
- # Date object of today's date.
476
- def today # :nodoc:
477
- @today
478
- end
479
-
480
- ##
481
- # The file that contains the previous entry. If previous entry was same month,
482
- # previous_file will be the same as file. If previous entry was last month,
483
- # and a file exists for last month, previous_file is last month's file.
484
- # If neither is true, returns an empty string.
485
- def get_previous_file # :nodoc:
486
- return file if File.file?(file) && !File.zero?(file)
487
- prev_month_file = File.expand_path(File.join(
488
- directory,
489
- today.prev_month.strftime(file_name_format)
490
- ))
491
- File.file?(prev_month_file) ? prev_month_file : ''
492
- end
493
-
494
- def get_all_previous_entries # :nodoc:
495
- return {} unless File.file?(previous_file)
496
- prev_entries = {}
497
- entry_header = ''
498
- section_type = ''
499
- File.foreach(previous_file) do |line|
500
- line.chomp!
501
- next if line.strip.empty?
502
- if line.match(%r{^#{'#' * header_depth}\s+})
503
- entry_header = line.sub(%r{^\#{#{header_depth}}\s*}, '')
504
- section_type = notes_header
505
- prev_entries[entry_header] ||= {}
506
- elsif line.match(%r{^#{'#' * sub_header_depth}\s+})
507
- section_type = determine_section_type(
508
- line.sub(%r{^\#{#{sub_header_depth}}\s*}, '')
509
- )
510
- prev_entries[entry_header][section_type] = []
511
- else
512
- prev_entries[entry_header][section_type] << line.sub(
513
- %r{\s*#{bullet_character}\s*}, ''
514
- )
515
- end
516
- end
517
- prev_entries
518
- rescue => e
519
- raise "File malformation: #{e}"
520
- end
521
-
522
- def determine_section_type(line) # :nodoc:
523
- [
524
- current_header,
525
- previous_header,
526
- impediments_header,
527
- notes_header
528
- ].each { |header| return header if line.include?(header) }
529
- raise "Unknown header type [#{line}]"
530
- end
531
-
532
- def previous_entry # :nodoc:
533
- all_previous_entries.each do |key, value|
534
- return value unless key == header
535
- end
51
+ # @param [String] file
52
+ def self.load_config_file(file)
53
+ file = ::File.expand_path(file)
54
+ raise "File #{file} does not exist." unless ::File.file?(file)
55
+ @config_file_loaded = true
56
+ load file
536
57
  end
537
58
  end