standup_md 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile.lock +9 -3
  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 +102 -1227
  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 +574 -0
  19. data/doc/created.rid +15 -8
  20. data/doc/index.html +192 -78
  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 +152 -247
  26. data/lib/standup_md.rb +29 -501
  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 +172 -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 -4
  41. data/doc/TestHelper.html +0 -282
  42. data/doc/TestStandupMD.html +0 -1938
@@ -1,530 +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
- ##
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
23
  ##
223
- # Constructor. Yields the instance so you can pass a block to access setters.
24
+ # Reset all configuration values to their defaults.
224
25
  #
225
- # @return [self]
226
- #
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
35
+ # StandupMD.configure { |s| s.cli.editor = 'mate' }
36
+ def self.configure
37
+ yield self.config
269
38
  end
270
39
 
271
40
  ##
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
313
- end
314
-
315
- ##
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
- end
421
- end
422
- @file_written = true
423
- end
424
-
425
- ##
426
- # Sets internal instance variables. Called when first instantiated, or after
427
- # directory is set.
428
- #
429
- # @return [self]
430
- def load
431
- FileUtils.mkdir_p(directory) unless File.directory?(directory)
432
-
433
- @today = Date.today
434
- @header = today.strftime(header_date_format)
435
- @file_written = false
436
- @file = File.expand_path(File.join(directory, today.strftime(file_name_format)))
437
- @previous_file = get_previous_file
438
- @all_previous_entries = get_all_previous_entries
439
- @entry_previously_added = all_previous_entries.key?(header)
440
- @previous_entry_tasks = previous_entry[current_header]
441
- @current_entry = @all_previous_entries.delete(header) || new_entry
442
- @all_entries = {header => current_entry}.merge(all_previous_entries)
443
-
444
- FileUtils.touch(file) unless File.file?(file)
445
- self
44
+ def self.config_file_loaded?
45
+ @config_file_loaded
446
46
  end
447
47
 
448
48
  ##
449
- # Alias of +load+
49
+ # Loads a config file.
450
50
  #
451
- # @return [self]
452
- alias_method :reload, :load
453
-
454
- private
455
-
456
- ##
457
- # Scaffolding with which new entries will be created.
458
- def new_entry # :nodoc:
459
- {
460
- previous_header => previous_entry_tasks || [],
461
- current_header => current_entry_tasks,
462
- impediments_header => impediments,
463
- notes_header => notes,
464
- }
465
- end
466
-
467
- ##
468
- # Date object of today's date.
469
- def today # :nodoc:
470
- @today
471
- end
472
-
473
- ##
474
- # The file that contains the previous entry. If previous entry was same month,
475
- # previous_file will be the same as file. If previous entry was last month,
476
- # and a file exists for last month, previous_file is last month's file.
477
- # If neither is true, returns an empty string.
478
- def get_previous_file # :nodoc:
479
- return file if File.file?(file) && !File.zero?(file)
480
- prev_month_file = File.expand_path(File.join(
481
- directory,
482
- today.prev_month.strftime(file_name_format)
483
- ))
484
- File.file?(prev_month_file) ? prev_month_file : ''
485
- end
486
-
487
- def get_all_previous_entries # :nodoc:
488
- return {} unless File.file?(previous_file)
489
- prev_entries = {}
490
- entry_header = ''
491
- section_type = ''
492
- File.foreach(previous_file) do |line|
493
- line.chomp!
494
- next if line.strip.empty?
495
- if line.match(%r{^#{'#' * header_depth}\s+})
496
- entry_header = line.sub(%r{^\#{#{header_depth}}\s*}, '')
497
- section_type = notes_header
498
- prev_entries[entry_header] ||= {}
499
- elsif line.match(%r{^#{'#' * sub_header_depth}\s+})
500
- section_type = determine_section_type(
501
- line.sub(%r{^\#{#{sub_header_depth}}\s*}, '')
502
- )
503
- prev_entries[entry_header][section_type] = []
504
- else
505
- prev_entries[entry_header][section_type] << line.sub(
506
- %r{\s*#{bullet_character}\s*}, ''
507
- )
508
- end
509
- end
510
- prev_entries
511
- rescue => e
512
- raise "File malformation: #{e}"
513
- end
514
-
515
- def determine_section_type(line) # :nodoc:
516
- [
517
- current_header,
518
- previous_header,
519
- impediments_header,
520
- notes_header
521
- ].each { |header| return header if line.include?(header) }
522
- raise "Unknown header type [#{line}]"
523
- end
524
-
525
- def previous_entry # :nodoc:
526
- all_previous_entries.each do |key, value|
527
- return value unless key == header
528
- 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
529
57
  end
530
58
  end