standup_md 1.0.1 → 2.0.1

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}_tasks")
22
- next if tasks.empty?
20
+ $stdout.print markdown.render_entry(entry)
21
+ end
23
22
 
24
- puts sub_header(header_type)
25
- 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)
@@ -51,7 +61,10 @@ module StandupMD
51
61
  opts.on(
52
62
  "--current ARRAY", Array,
53
63
  "List of current entry's tasks"
54
- ) { |v| config.entry.current = v }
64
+ ) do |v|
65
+ @current_option_passed = true
66
+ config.entry.current = v
67
+ end
55
68
 
56
69
  opts.on(
57
70
  "--previous ARRAY", Array,
@@ -68,21 +81,6 @@ module StandupMD
68
81
  "List of notes for current entry"
69
82
  ) { |v| config.entry.notes = v }
70
83
 
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
84
  opts.on(
87
85
  "-E", "--editor EDITOR",
88
86
  "Editor to use for opening standup files"
@@ -90,7 +88,7 @@ module StandupMD
90
88
 
91
89
  opts.on(
92
90
  "-d", "--directory DIRECTORY",
93
- "The directories where standup files are located"
91
+ "The directory where standup files are located"
94
92
  ) { |v| config.file.directory = v }
95
93
 
96
94
  opts.on(
@@ -122,12 +120,26 @@ module StandupMD
122
120
  "-p", "--print [DATE]",
123
121
  "Print current entry.",
124
122
  "If DATE is passed, will print entry for DATE, if it exists.",
125
- "DATE must be in the same format as file-name-format"
123
+ "DATE must be in the same format as the entry header date."
126
124
  ) do |v|
127
125
  config.cli.print = true
128
126
  config.cli.date =
129
127
  v.nil? ? Date.today : Date.strptime(v, config.file.header_date_format)
130
128
  end
129
+
130
+ opts.on(
131
+ "-P", "--post [PLATFORM]",
132
+ "Post current entry to a chat client. Defaults to Slack.",
133
+ "If PLATFORM is passed, use that post adapter."
134
+ ) do |v|
135
+ config.cli.post = true
136
+ config.cli.post_adapter = v.nil? ? config.post.default_adapter : v.to_sym
137
+ end
138
+
139
+ opts.on(
140
+ "--post-channel CHANNEL",
141
+ "Channel, room, or conversation to post to"
142
+ ) { |v| config.cli.post_channel = v }
131
143
  end.parse!(options)
132
144
  if zsh_completion_requested?
133
145
  raise OptionParser::InvalidArgument, options.join(" ") unless options.empty?
@@ -148,7 +160,11 @@ module StandupMD
148
160
  # @return [StandupMD::Entry]
149
161
  def new_entry(file)
150
162
  entry = file.entries.find(config.cli.date)
151
- return entry if read_only? || entry || config.cli.date != Date.today
163
+ return entry if read_only? || config.cli.date != Date.today
164
+ if entry
165
+ append_current_entry(entry) if current_option_passed?
166
+ return entry
167
+ end
152
168
 
153
169
  StandupMD::Entry.new(
154
170
  config.cli.date,
@@ -165,11 +181,23 @@ module StandupMD
165
181
  # @return [Array]
166
182
  def previous_entry(file)
167
183
  return config.entry.previous unless config.cli.auto_fill_previous
168
- return prev_entry_tasks(prev_file.load.entries) if file.new? && prev_file
184
+ if file.new?
185
+ previous_file = prev_file_exists?
186
+ return prev_entry_tasks(previous_file.load.entries) if previous_file
187
+ end
169
188
 
170
189
  prev_entry_tasks(file.entries)
171
190
  end
172
191
 
192
+ def append_current_entry(entry)
193
+ current = entry.section(:current)
194
+ config.entry.current.each { |task| current << task }
195
+ end
196
+
197
+ def current_option_passed?
198
+ @current_option_passed
199
+ end
200
+
173
201
  ##
174
202
  # Parses the optional file date argument.
175
203
  #
@@ -202,34 +230,24 @@ module StandupMD
202
230
  ##
203
231
  # The previous month's file.
204
232
  #
233
+ # @param [StandupMD::Config::File] config
234
+ #
205
235
  # @return [StandupMD::File]
206
- def prev_file
207
- StandupMD::File.find_by_date(Date.today.prev_month)
236
+ def prev_file(config: self.config.file)
237
+ StandupMD::File.find_by_date(Date.today.prev_month, config: config)
208
238
  end
209
239
 
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)
240
+ def prev_file_exists?
241
+ without_file_creation { |file_config| prev_file(config: file_config) }
242
+ rescue StandupMD::File::NotFoundError
243
+ nil
219
244
  end
220
245
 
221
246
  ##
222
- # The sub-header.
223
- #
224
- # @param [String] header_type
247
+ # Markdown renderer used for CLI output.
225
248
  #
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
249
+ # @return [StandupMD::Parsers::Markdown]
250
+ def markdown
233
251
  StandupMD::Parsers::Markdown.new(config.file)
234
252
  end
235
253
  end
@@ -5,7 +5,7 @@ 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
 
@@ -22,7 +22,7 @@ module StandupMD
22
22
  #
23
23
  # @return [StandupMD::Config::Cli]
24
24
  def self.config
25
- @config ||= StandupMD.config.cli
25
+ StandupMD.config.cli
26
26
  end
27
27
 
28
28
  ##
@@ -73,14 +73,22 @@ module StandupMD
73
73
  end
74
74
 
75
75
  exe.write_file if exe.write?
76
- if config.print
76
+ if exe.config.cli.print
77
77
  exe.print(exe.entry)
78
- elsif config.edit
78
+ elsif exe.config.cli.post
79
+ exe.post(exe.entry)
80
+ elsif exe.config.cli.edit
79
81
  exe.edit
80
82
  end
81
83
  end
82
84
  end
83
85
 
86
+ ##
87
+ # Runtime configuration snapshot for this CLI invocation.
88
+ #
89
+ # @return [StandupMD::Config]
90
+ attr_reader :config
91
+
84
92
  ##
85
93
  # The entry searched for, usually today.
86
94
  #
@@ -120,14 +128,16 @@ module StandupMD
120
128
  #
121
129
  # @param [Array] options
122
130
  def initialize(options = [], load_config: true)
123
- @config = self.class.config
131
+ @config = nil
124
132
  @preference_file_loaded = false
125
133
  @file_date_argument = false
134
+ @current_option_passed = false
126
135
  @zsh_completion_requested = false
127
136
  @options = options
128
137
  return if load_zsh_completion_request(options)
129
138
 
130
139
  load_preferences if load_config
140
+ @config = StandupMD.config.copy
131
141
  load_runtime_preferences(options)
132
142
  return if zsh_completion_requested?
133
143
 
@@ -141,11 +151,12 @@ module StandupMD
141
151
  #
142
152
  # @return [nil]
143
153
  def load_preferences
144
- if ::File.exist?(@config.preference_file)
145
- ::StandupMD.load_config_file(@config.preference_file)
154
+ preference_file = StandupMD.config.cli.preference_file
155
+ if ::File.exist?(preference_file)
156
+ ::StandupMD.load_config_file(preference_file)
146
157
  @preference_file_loaded = true
147
158
  else
148
- echo "Preference file #{@config.preference_file} does not exist."
159
+ self.class.echo "Preference file #{preference_file} does not exist."
149
160
  end
150
161
  end
151
162
 
@@ -162,8 +173,8 @@ module StandupMD
162
173
  #
163
174
  # @return [nil]
164
175
  def edit
165
- echo "Opening file in #{@config.editor}"
166
- exec("#{@config.editor} #{file.name}")
176
+ echo "Opening file in #{@config.cli.editor}"
177
+ exec("#{@config.cli.editor} #{file.name}")
167
178
  end
168
179
 
169
180
  ##
@@ -180,7 +191,15 @@ module StandupMD
180
191
  #
181
192
  # @return [Boolean]
182
193
  def write?
183
- !!(@config.write && !read_only? && entry)
194
+ !!(@config.cli.write && !read_only? && entry)
195
+ end
196
+
197
+ ##
198
+ # Should the CLI post the entry to a chat adapter?
199
+ #
200
+ # @return [Boolean]
201
+ def post?
202
+ @config.cli.post
184
203
  end
185
204
 
186
205
  ##
@@ -188,7 +207,7 @@ module StandupMD
188
207
  #
189
208
  # @return [nil]
190
209
  def echo(msg)
191
- self.class.echo(msg)
210
+ puts msg if @config&.cli&.verbose
192
211
  end
193
212
 
194
213
  private
@@ -211,7 +230,7 @@ module StandupMD
211
230
  #
212
231
  # @return [Boolean]
213
232
  def read_only?
214
- @config.print || file_date_argument?
233
+ @config.cli.print || @config.cli.post || file_date_argument?
215
234
  end
216
235
 
217
236
  ##
@@ -219,11 +238,13 @@ module StandupMD
219
238
  #
220
239
  # @return [StandupMD::File, nil]
221
240
  def find_file
222
- return StandupMD::File.find_by_date(@config.date) unless read_only?
241
+ return StandupMD::File.find_by_date(@config.cli.date, config: @config.file) unless read_only?
223
242
 
224
- without_file_creation { StandupMD::File.find_by_date(@config.date) }
225
- rescue
226
- raise unless @config.print
243
+ without_file_creation do |file_config|
244
+ StandupMD::File.find_by_date(@config.cli.date, config: file_config)
245
+ end
246
+ rescue StandupMD::File::NotFoundError
247
+ raise unless @config.cli.print || @config.cli.post
227
248
 
228
249
  nil
229
250
  end
@@ -233,11 +254,9 @@ module StandupMD
233
254
  #
234
255
  # @return [StandupMD::File]
235
256
  def without_file_creation
236
- original_create = config.file.create
237
- config.file.create = false
238
- yield
239
- ensure
240
- config.file.create = original_create
257
+ file_config = @config.file.copy
258
+ file_config.create = false
259
+ yield file_config
241
260
  end
242
261
  end
243
262
  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