standup_md 1.0.0 → 2.0.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.
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "standup_md/parsers/markdown"
4
+ require "standup_md/post"
4
5
 
5
6
  module StandupMD
6
7
  class Cli
7
8
  ##
8
- # Module responsible for reading and writing standup files.
9
+ # Helpers for CLI commands and option handling.
9
10
  module Helpers
10
11
  ##
11
12
  # Print an entry to the command line.
@@ -16,17 +17,26 @@ module StandupMD
16
17
  def print(entry)
17
18
  return puts "No record found for #{config.cli.date}" if entry.nil?
18
19
 
19
- puts header(entry)
20
- config.file.sub_header_order.each do |header_type|
21
- tasks = entry.public_send(header_type)
22
- next if !tasks || tasks.empty?
20
+ $stdout.print markdown.render_entry(entry)
21
+ end
23
22
 
24
- puts sub_header(header_type)
25
- entry.public_send("#{header_type}_tasks").each do |task|
26
- puts parser.task_line(task)
27
- end
28
- end
29
- puts
23
+ ##
24
+ # Post an entry to the configured chat adapter.
25
+ #
26
+ # @param [StandupMD::Entry] entry
27
+ #
28
+ # @return [StandupMD::Post::Result, nil]
29
+ def post(entry)
30
+ return puts "No record found for #{config.cli.date}" if entry.nil?
31
+
32
+ result = StandupMD::Post.post(
33
+ entry,
34
+ adapter: config.cli.post_adapter,
35
+ channel: config.cli.post_channel,
36
+ config: config
37
+ )
38
+ puts "Could not post to #{result.adapter}: #{result.error}" if result.failure?
39
+ result
30
40
  end
31
41
 
32
42
  private
@@ -36,12 +46,12 @@ module StandupMD
36
46
  #
37
47
  # @return [StandupMD::Config]
38
48
  def config # :nodoc:
39
- StandupMD.config
49
+ @config
40
50
  end
41
51
 
42
52
  ##
43
- # Parses options passed at runtime and concatenates them with the options
44
- # in the user's preferences file. Reveal source to see options.
53
+ # Parses options passed at runtime into this CLI invocation's config
54
+ # snapshot. Reveal source to see options.
45
55
  #
46
56
  # @return [Hash]
47
57
  def load_runtime_preferences(options)
@@ -55,7 +65,7 @@ module StandupMD
55
65
 
56
66
  opts.on(
57
67
  "--previous ARRAY", Array,
58
- "List of precious entry's tasks"
68
+ "List of previous entry's tasks"
59
69
  ) { |v| config.entry.previous = v }
60
70
 
61
71
  opts.on(
@@ -68,21 +78,6 @@ module StandupMD
68
78
  "List of notes for current entry"
69
79
  ) { |v| config.entry.notes = v }
70
80
 
71
- opts.on(
72
- "--sub-header-order ARRAY", Array,
73
- "The order of the sub-headers when writing the file"
74
- ) { |v| config.file.sub_header_order = v }
75
-
76
- opts.on(
77
- "--indent-width INTEGER", Integer,
78
- "Number of spaces used for each nested task level"
79
- ) { |v| config.file.indent_width = v }
80
-
81
- opts.on(
82
- "-f", "--file-name-format STRING",
83
- "Date-formattable string to use for standup file name"
84
- ) { |v| config.file.name_format = v }
85
-
86
81
  opts.on(
87
82
  "-E", "--editor EDITOR",
88
83
  "Editor to use for opening standup files"
@@ -90,7 +85,7 @@ module StandupMD
90
85
 
91
86
  opts.on(
92
87
  "-d", "--directory DIRECTORY",
93
- "The directories where standup files are located"
88
+ "The directory where standup files are located"
94
89
  ) { |v| config.file.directory = v }
95
90
 
96
91
  opts.on(
@@ -122,12 +117,26 @@ module StandupMD
122
117
  "-p", "--print [DATE]",
123
118
  "Print current entry.",
124
119
  "If DATE is passed, will print entry for DATE, if it exists.",
125
- "DATE must be in the same format as file-name-format"
120
+ "DATE must be in the same format as the entry header date."
126
121
  ) do |v|
127
122
  config.cli.print = true
128
123
  config.cli.date =
129
124
  v.nil? ? Date.today : Date.strptime(v, config.file.header_date_format)
130
125
  end
126
+
127
+ opts.on(
128
+ "-P", "--post [PLATFORM]",
129
+ "Post current entry to a chat client. Defaults to Slack.",
130
+ "If PLATFORM is passed, use that post adapter."
131
+ ) do |v|
132
+ config.cli.post = true
133
+ config.cli.post_adapter = v.nil? ? config.post.default_adapter : v.to_sym
134
+ end
135
+
136
+ opts.on(
137
+ "--post-channel CHANNEL",
138
+ "Channel, room, or conversation to post to"
139
+ ) { |v| config.cli.post_channel = v }
131
140
  end.parse!(options)
132
141
  if zsh_completion_requested?
133
142
  raise OptionParser::InvalidArgument, options.join(" ") unless options.empty?
@@ -160,14 +169,17 @@ module StandupMD
160
169
  end
161
170
 
162
171
  ##
163
- # The "previous" entries.
172
+ # The "previous" tasks.
164
173
  #
165
174
  # @return [Array]
166
175
  def previous_entry(file)
167
- return config.entry.previous_entry unless config.cli.auto_fill_previous
168
- return prev_entry(prev_file.load.entries) if file.new? && prev_file
176
+ return config.entry.previous unless config.cli.auto_fill_previous
177
+ if file.new?
178
+ previous_file = prev_file_exists?
179
+ return prev_entry_tasks(previous_file.load.entries) if previous_file
180
+ end
169
181
 
170
- prev_entry(file.entries)
182
+ prev_entry_tasks(file.entries)
171
183
  end
172
184
 
173
185
  ##
@@ -190,46 +202,36 @@ module StandupMD
190
202
  end
191
203
 
192
204
  ##
193
- # The previous entry.
205
+ # The previous entry's current tasks.
194
206
  #
195
207
  # @param [StandupMD::EntryList] entries
196
208
  #
197
- # @return [StandupMD::Entry]
198
- def prev_entry(entries)
199
- entries.empty? ? [] : entries.last.current
209
+ # @return [Array<StandupMD::Task>]
210
+ def prev_entry_tasks(entries)
211
+ entries.empty? ? [] : entries.last.current_tasks
200
212
  end
201
213
 
202
214
  ##
203
215
  # The previous month's file.
204
216
  #
217
+ # @param [StandupMD::Config::File] config
218
+ #
205
219
  # @return [StandupMD::File]
206
- def prev_file
207
- StandupMD::File.find_by_date(Date.today.prev_month)
220
+ def prev_file(config: self.config.file)
221
+ StandupMD::File.find_by_date(Date.today.prev_month, config: config)
208
222
  end
209
223
 
210
- ##
211
- # The header.
212
- #
213
- # @param [StandupMD::Entry] entry
214
- #
215
- # @return [String]
216
- def header(entry)
217
- "#" * config.file.header_depth + " " +
218
- entry.date.strftime(config.file.header_date_format)
224
+ def prev_file_exists?
225
+ without_file_creation { |file_config| prev_file(config: file_config) }
226
+ rescue StandupMD::File::NotFoundError
227
+ nil
219
228
  end
220
229
 
221
230
  ##
222
- # The sub-header.
231
+ # Markdown renderer used for CLI output.
223
232
  #
224
- # @param [String] header_type
225
- #
226
- # @return [String]
227
- def sub_header(header_type)
228
- "#" * config.file.sub_header_depth + " " +
229
- config.file.public_send("#{header_type}_header")
230
- end
231
-
232
- def parser
233
+ # @return [StandupMD::Parsers::Markdown]
234
+ def markdown
233
235
  StandupMD::Parsers::Markdown.new(config.file)
234
236
  end
235
237
  end
@@ -5,10 +5,14 @@ require "standup_md/cli/helpers"
5
5
 
6
6
  module StandupMD
7
7
  ##
8
- # Class for handing the command-line interface.
8
+ # Class for handling the command-line interface.
9
9
  class Cli
10
10
  include Helpers
11
11
 
12
+ ##
13
+ # Path to the bundled zsh completion script.
14
+ #
15
+ # @return [String]
12
16
  ZSH_COMPLETION_FILE = ::File.expand_path(
13
17
  ::File.join(__dir__, "..", "..", "completion", "zsh", "_standup")
14
18
  ).freeze
@@ -18,7 +22,7 @@ module StandupMD
18
22
  #
19
23
  # @return [StandupMD::Config::Cli]
20
24
  def self.config
21
- @config ||= StandupMD.config.cli
25
+ StandupMD.config.cli
22
26
  end
23
27
 
24
28
  ##
@@ -69,14 +73,22 @@ module StandupMD
69
73
  end
70
74
 
71
75
  exe.write_file if exe.write?
72
- if config.print
76
+ if exe.config.cli.print
73
77
  exe.print(exe.entry)
74
- elsif config.edit
78
+ elsif exe.config.cli.post
79
+ exe.post(exe.entry)
80
+ elsif exe.config.cli.edit
75
81
  exe.edit
76
82
  end
77
83
  end
78
84
  end
79
85
 
86
+ ##
87
+ # Runtime configuration snapshot for this CLI invocation.
88
+ #
89
+ # @return [StandupMD::Config]
90
+ attr_reader :config
91
+
80
92
  ##
81
93
  # The entry searched for, usually today.
82
94
  #
@@ -116,7 +128,7 @@ module StandupMD
116
128
  #
117
129
  # @param [Array] options
118
130
  def initialize(options = [], load_config: true)
119
- @config = self.class.config
131
+ @config = nil
120
132
  @preference_file_loaded = false
121
133
  @file_date_argument = false
122
134
  @zsh_completion_requested = false
@@ -124,6 +136,7 @@ module StandupMD
124
136
  return if load_zsh_completion_request(options)
125
137
 
126
138
  load_preferences if load_config
139
+ @config = StandupMD.config.copy
127
140
  load_runtime_preferences(options)
128
141
  return if zsh_completion_requested?
129
142
 
@@ -137,11 +150,12 @@ module StandupMD
137
150
  #
138
151
  # @return [nil]
139
152
  def load_preferences
140
- if ::File.exist?(@config.preference_file)
141
- ::StandupMD.load_config_file(@config.preference_file)
153
+ preference_file = StandupMD.config.cli.preference_file
154
+ if ::File.exist?(preference_file)
155
+ ::StandupMD.load_config_file(preference_file)
142
156
  @preference_file_loaded = true
143
157
  else
144
- echo "Preference file #{@config.preference_file} does not exist."
158
+ self.class.echo "Preference file #{preference_file} does not exist."
145
159
  end
146
160
  end
147
161
 
@@ -158,8 +172,8 @@ module StandupMD
158
172
  #
159
173
  # @return [nil]
160
174
  def edit
161
- echo "Opening file in #{@config.editor}"
162
- exec("#{@config.editor} #{file.name}")
175
+ echo "Opening file in #{@config.cli.editor}"
176
+ exec("#{@config.cli.editor} #{file.name}")
163
177
  end
164
178
 
165
179
  ##
@@ -176,7 +190,15 @@ module StandupMD
176
190
  #
177
191
  # @return [Boolean]
178
192
  def write?
179
- !!(@config.write && !read_only? && entry)
193
+ !!(@config.cli.write && !read_only? && entry)
194
+ end
195
+
196
+ ##
197
+ # Should the CLI post the entry to a chat adapter?
198
+ #
199
+ # @return [Boolean]
200
+ def post?
201
+ @config.cli.post
180
202
  end
181
203
 
182
204
  ##
@@ -184,7 +206,7 @@ module StandupMD
184
206
  #
185
207
  # @return [nil]
186
208
  def echo(msg)
187
- self.class.echo(msg)
209
+ puts msg if @config&.cli&.verbose
188
210
  end
189
211
 
190
212
  private
@@ -207,7 +229,7 @@ module StandupMD
207
229
  #
208
230
  # @return [Boolean]
209
231
  def read_only?
210
- @config.print || file_date_argument?
232
+ @config.cli.print || @config.cli.post || file_date_argument?
211
233
  end
212
234
 
213
235
  ##
@@ -215,11 +237,13 @@ module StandupMD
215
237
  #
216
238
  # @return [StandupMD::File, nil]
217
239
  def find_file
218
- return StandupMD::File.find_by_date(@config.date) unless read_only?
240
+ return StandupMD::File.find_by_date(@config.cli.date, config: @config.file) unless read_only?
219
241
 
220
- without_file_creation { StandupMD::File.find_by_date(@config.date) }
221
- rescue
222
- raise unless @config.print
242
+ without_file_creation do |file_config|
243
+ StandupMD::File.find_by_date(@config.cli.date, config: file_config)
244
+ end
245
+ rescue StandupMD::File::NotFoundError
246
+ raise unless @config.cli.print || @config.cli.post
223
247
 
224
248
  nil
225
249
  end
@@ -229,11 +253,9 @@ module StandupMD
229
253
  #
230
254
  # @return [StandupMD::File]
231
255
  def without_file_creation
232
- original_create = config.file.create
233
- config.file.create = false
234
- yield
235
- ensure
236
- config.file.create = original_create
256
+ file_config = @config.file.copy
257
+ file_config.create = false
258
+ yield file_config
237
259
  end
238
260
  end
239
261
  end
@@ -12,18 +12,27 @@ module StandupMD
12
12
  #
13
13
  # @return [Hash]
14
14
  DEFAULTS = {
15
- date: Date.today,
16
- editor: ENV["VISUAL"] || ENV["EDITOR"] || "vim",
15
+ date: -> { Date.today },
16
+ editor: -> { ENV["VISUAL"] || ENV["EDITOR"] || "vim" },
17
17
  verbose: false,
18
18
  edit: true,
19
19
  write: true,
20
20
  print: false,
21
+ post: false,
22
+ post_adapter: nil,
23
+ post_channel: nil,
21
24
  auto_fill_previous: true,
22
25
  preference_file: ::File.expand_path(
23
26
  ::File.join(ENV["HOME"], ".standuprc")
24
27
  )
25
28
  }.freeze
26
29
 
30
+ ##
31
+ # Attributes copied into request-scoped config snapshots.
32
+ #
33
+ # @return [Array<Symbol>]
34
+ CONFIG_ATTRIBUTES = DEFAULTS.keys.freeze
35
+
27
36
  ##
28
37
  # The editor to use when opening standup files. If one is not set, the
29
38
  # first of $VISUAL, $EDITOR, or vim will be used, in that order.
@@ -34,7 +43,7 @@ module StandupMD
34
43
  attr_accessor :editor
35
44
 
36
45
  ##
37
- # Should the cli print verbose output?
46
+ # Should the CLI print verbose output?
38
47
  #
39
48
  # @param [Boolean] verbose
40
49
  #
@@ -42,7 +51,7 @@ module StandupMD
42
51
  attr_accessor :verbose
43
52
 
44
53
  ##
45
- # Should the cli edit?
54
+ # Should the CLI edit?
46
55
  #
47
56
  # @param [Boolean] edit
48
57
  #
@@ -50,7 +59,7 @@ module StandupMD
50
59
  attr_accessor :edit
51
60
 
52
61
  ##
53
- # Should the cli automatically write the new entry to the file?
62
+ # Should the CLI automatically write the new entry to the file?
54
63
  #
55
64
  # @param [Boolean] write
56
65
  #
@@ -58,13 +67,37 @@ module StandupMD
58
67
  attr_accessor :write
59
68
 
60
69
  ##
61
- # Should the cli print the entry to the command line?
70
+ # Should the CLI print the entry to the command line?
62
71
  #
63
72
  # @param [Boolean] print
64
73
  #
65
74
  # @return [Boolean]
66
75
  attr_accessor :print
67
76
 
77
+ ##
78
+ # Should the CLI post the entry to a chat client?
79
+ #
80
+ # @param [Boolean] post
81
+ #
82
+ # @return [Boolean]
83
+ attr_accessor :post
84
+
85
+ ##
86
+ # The chat adapter to use for posting.
87
+ #
88
+ # @param [String, Symbol, nil] post_adapter
89
+ #
90
+ # @return [String, Symbol, nil]
91
+ attr_accessor :post_adapter
92
+
93
+ ##
94
+ # The channel to use for posting.
95
+ #
96
+ # @param [String, nil] post_channel
97
+ #
98
+ # @return [String, nil]
99
+ attr_accessor :post_channel
100
+
68
101
  ##
69
102
  # The date to use to find the entry.
70
103
  #
@@ -101,7 +134,37 @@ module StandupMD
101
134
  #
102
135
  # @return [Hash]
103
136
  def reset
104
- DEFAULTS.each { |k, v| instance_variable_set("@#{k}", v) }
137
+ DEFAULTS.each do |key, value|
138
+ instance_variable_set("@#{key}", copy_default(resolve_default(value)))
139
+ end
140
+ end
141
+
142
+ ##
143
+ # Copies values from another CLI config.
144
+ #
145
+ # @param [StandupMD::Config::Cli] config
146
+ #
147
+ # @return [StandupMD::Config::Cli]
148
+ def copy_from(config)
149
+ CONFIG_ATTRIBUTES.each do |attribute|
150
+ instance_variable_set(
151
+ "@#{attribute}",
152
+ copy_default(config.public_send(attribute))
153
+ )
154
+ end
155
+ self
156
+ end
157
+
158
+ private
159
+
160
+ def resolve_default(value)
161
+ value.respond_to?(:call) ? value.call : value
162
+ end
163
+
164
+ def copy_default(value)
165
+ return value.dup if value.is_a?(Array) || value.is_a?(Hash)
166
+
167
+ value
105
168
  end
106
169
  end
107
170
  end
@@ -16,6 +16,12 @@ module StandupMD
16
16
  notes: []
17
17
  }.freeze
18
18
 
19
+ ##
20
+ # Attributes copied into request-scoped config snapshots.
21
+ #
22
+ # @return [Array<Symbol>]
23
+ CONFIG_ATTRIBUTES = DEFAULTS.keys.freeze
24
+
19
25
  ##
20
26
  # Tasks for "Current" section.
21
27
  #
@@ -59,7 +65,31 @@ module StandupMD
59
65
  #
60
66
  # @return [Hash]
61
67
  def reset
62
- DEFAULTS.each { |k, v| instance_variable_set("@#{k}", v) }
68
+ DEFAULTS.each { |k, v| instance_variable_set("@#{k}", copy_default(v)) }
69
+ end
70
+
71
+ ##
72
+ # Copies values from another entry config.
73
+ #
74
+ # @param [StandupMD::Config::Entry] config
75
+ #
76
+ # @return [StandupMD::Config::Entry]
77
+ def copy_from(config)
78
+ CONFIG_ATTRIBUTES.each do |attribute|
79
+ instance_variable_set(
80
+ "@#{attribute}",
81
+ copy_default(config.public_send(attribute))
82
+ )
83
+ end
84
+ self
85
+ end
86
+
87
+ private
88
+
89
+ def copy_default(value)
90
+ return value.dup if value.is_a?(Array) || value.is_a?(Hash)
91
+
92
+ value
63
93
  end
64
94
  end
65
95
  end
@@ -25,6 +25,12 @@ module StandupMD
25
25
  create: true
26
26
  }.freeze
27
27
 
28
+ ##
29
+ # Attributes copied into request-scoped config snapshots.
30
+ #
31
+ # @return [Array<Symbol>]
32
+ CONFIG_ATTRIBUTES = DEFAULTS.keys.freeze
33
+
28
34
  ##
29
35
  # Number of octothorps that should preface entry headers.
30
36
  #
@@ -153,7 +159,31 @@ module StandupMD
153
159
  #
154
160
  # @return [Hash]
155
161
  def reset
156
- DEFAULTS.each { |k, v| instance_variable_set("@#{k}", v) }
162
+ DEFAULTS.each { |k, v| instance_variable_set("@#{k}", copy_default(v)) }
163
+ end
164
+
165
+ ##
166
+ # Builds an independent copy of this file config.
167
+ #
168
+ # @return [StandupMD::Config::File]
169
+ def copy
170
+ self.class.new.copy_from(self)
171
+ end
172
+
173
+ ##
174
+ # Copies values from another file config.
175
+ #
176
+ # @param [StandupMD::Config::File] config
177
+ #
178
+ # @return [StandupMD::Config::File]
179
+ def copy_from(config)
180
+ CONFIG_ATTRIBUTES.each do |attribute|
181
+ instance_variable_set(
182
+ "@#{attribute}",
183
+ copy_default(config.public_send(attribute))
184
+ )
185
+ end
186
+ self
157
187
  end
158
188
 
159
189
  ##
@@ -209,17 +239,21 @@ module StandupMD
209
239
 
210
240
  ##
211
241
  # Setter for directory. Must be expanded in case the user uses `~` for
212
- # home. If the directory doesn't exist, it will be created. To reset
213
- # instance variables after changing the directory, you'll need to call
214
- # load.
242
+ # home. Directory creation is handled by StandupMD::File.
215
243
  #
216
244
  # @param [String] directory
217
245
  #
218
246
  # @return [String]
219
247
  def directory=(directory)
220
- @directory = ::File.expand_path(directory).tap do |directory|
221
- FileUtils.mkdir_p(directory) unless ::File.directory?(directory)
222
- end
248
+ @directory = ::File.expand_path(directory)
249
+ end
250
+
251
+ private
252
+
253
+ def copy_default(value)
254
+ return value.dup if value.is_a?(Array) || value.is_a?(Hash)
255
+
256
+ value
223
257
  end
224
258
  end
225
259
  end