standup_md 1.0.1 → 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.
@@ -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,7 +128,7 @@ 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
126
134
  @zsh_completion_requested = false
@@ -128,6 +136,7 @@ module StandupMD
128
136
  return if load_zsh_completion_request(options)
129
137
 
130
138
  load_preferences if load_config
139
+ @config = StandupMD.config.copy
131
140
  load_runtime_preferences(options)
132
141
  return if zsh_completion_requested?
133
142
 
@@ -141,11 +150,12 @@ module StandupMD
141
150
  #
142
151
  # @return [nil]
143
152
  def load_preferences
144
- if ::File.exist?(@config.preference_file)
145
- ::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)
146
156
  @preference_file_loaded = true
147
157
  else
148
- echo "Preference file #{@config.preference_file} does not exist."
158
+ self.class.echo "Preference file #{preference_file} does not exist."
149
159
  end
150
160
  end
151
161
 
@@ -162,8 +172,8 @@ module StandupMD
162
172
  #
163
173
  # @return [nil]
164
174
  def edit
165
- echo "Opening file in #{@config.editor}"
166
- exec("#{@config.editor} #{file.name}")
175
+ echo "Opening file in #{@config.cli.editor}"
176
+ exec("#{@config.cli.editor} #{file.name}")
167
177
  end
168
178
 
169
179
  ##
@@ -180,7 +190,15 @@ module StandupMD
180
190
  #
181
191
  # @return [Boolean]
182
192
  def write?
183
- !!(@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
184
202
  end
185
203
 
186
204
  ##
@@ -188,7 +206,7 @@ module StandupMD
188
206
  #
189
207
  # @return [nil]
190
208
  def echo(msg)
191
- self.class.echo(msg)
209
+ puts msg if @config&.cli&.verbose
192
210
  end
193
211
 
194
212
  private
@@ -211,7 +229,7 @@ module StandupMD
211
229
  #
212
230
  # @return [Boolean]
213
231
  def read_only?
214
- @config.print || file_date_argument?
232
+ @config.cli.print || @config.cli.post || file_date_argument?
215
233
  end
216
234
 
217
235
  ##
@@ -219,11 +237,13 @@ module StandupMD
219
237
  #
220
238
  # @return [StandupMD::File, nil]
221
239
  def find_file
222
- 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?
223
241
 
224
- without_file_creation { StandupMD::File.find_by_date(@config.date) }
225
- rescue
226
- 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
227
247
 
228
248
  nil
229
249
  end
@@ -233,11 +253,9 @@ module StandupMD
233
253
  #
234
254
  # @return [StandupMD::File]
235
255
  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
256
+ file_config = @config.file.copy
257
+ file_config.create = false
258
+ yield file_config
241
259
  end
242
260
  end
243
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
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "standup_md/post"
4
+
5
+ module StandupMD
6
+ class Config
7
+ ##
8
+ # The configuration class for chat posting.
9
+ class Post
10
+ ##
11
+ # The default posting options.
12
+ #
13
+ # @return [Hash]
14
+ DEFAULTS = {
15
+ default_adapter: :slack,
16
+ title: nil
17
+ }.freeze
18
+
19
+ ##
20
+ # Attributes copied into request-scoped config snapshots.
21
+ #
22
+ # @return [Array<Symbol>]
23
+ CONFIG_ATTRIBUTES = DEFAULTS.keys.freeze
24
+
25
+ ##
26
+ # The adapter used when `standup --post` is called without a platform.
27
+ #
28
+ # @return [Symbol]
29
+ attr_accessor :default_adapter
30
+
31
+ ##
32
+ # Format string for posted entry titles.
33
+ #
34
+ # This only affects messages sent through chat posting adapters. It does
35
+ # not change stored standup markdown files. Use `%s` as a placeholder for
36
+ # the normal entry title, such as the entry date. This is useful when chat
37
+ # clients post through workspace apps or bots with shared names like
38
+ # "StandupMD", but the message should still identify whose standup it is.
39
+ #
40
+ # @example Include the person's name after the entry date
41
+ # StandupMD.config.post.title = "%s - Evan Gray"
42
+ #
43
+ # @return [String, nil]
44
+ attr_accessor :title
45
+
46
+ ##
47
+ # Registered adapter classes or instances.
48
+ #
49
+ # @return [Hash]
50
+ attr_reader :adapters
51
+
52
+ ##
53
+ # Non-secret adapter options.
54
+ #
55
+ # @return [Hash]
56
+ attr_reader :adapter_options
57
+
58
+ ##
59
+ # Initializes the config with default values.
60
+ def initialize
61
+ reset
62
+ end
63
+
64
+ ##
65
+ # Sets all config values back to their defaults.
66
+ #
67
+ # @return [Hash]
68
+ def reset
69
+ DEFAULTS.each { |k, v| instance_variable_set("@#{k}", v) }
70
+ @adapters = {}
71
+ @adapter_options = Hash.new { |hash, key| hash[key] = {} }
72
+ register_adapter(:slack, StandupMD::Post::Adapters::Slack)
73
+ DEFAULTS
74
+ end
75
+
76
+ ##
77
+ # Copies values from another post config.
78
+ #
79
+ # @param [StandupMD::Config::Post] config
80
+ #
81
+ # @return [StandupMD::Config::Post]
82
+ def copy_from(config)
83
+ CONFIG_ATTRIBUTES.each do |attribute|
84
+ instance_variable_set("@#{attribute}", config.public_send(attribute))
85
+ end
86
+ @adapters = config.adapters.dup
87
+ @adapter_options = Hash.new { |hash, key| hash[key] = {} }
88
+ config.adapter_options.each do |name, options|
89
+ @adapter_options[name] = options.dup
90
+ end
91
+ self
92
+ end
93
+
94
+ ##
95
+ # Registers a posting adapter.
96
+ #
97
+ # @param name [String, Symbol]
98
+ # @param adapter [Class, Object]
99
+ #
100
+ # @return [Class, Object]
101
+ def register_adapter(name, adapter)
102
+ adapters[name.to_sym] = adapter
103
+ end
104
+
105
+ ##
106
+ # Configures non-secret adapter options.
107
+ #
108
+ # @param name [String, Symbol]
109
+ # @param options [Hash]
110
+ #
111
+ # @return [Hash]
112
+ def configure_adapter(name, options = {})
113
+ options_for(name).merge!(symbolize_keys(options))
114
+ end
115
+
116
+ ##
117
+ # Returns non-secret options for an adapter.
118
+ #
119
+ # @param name [String, Symbol]
120
+ #
121
+ # @return [Hash]
122
+ def options_for(name)
123
+ adapter_options[name.to_sym]
124
+ end
125
+
126
+ ##
127
+ # Builds the adapter requested by name.
128
+ #
129
+ # @param name [String, Symbol, nil]
130
+ #
131
+ # @return [Object]
132
+ def build_adapter(name = nil)
133
+ adapter_name = (name || default_adapter).to_sym
134
+ adapter = adapters.fetch(adapter_name) do
135
+ raise StandupMD::Post::UnknownAdapter, "No post adapter registered for #{adapter_name}"
136
+ end
137
+ return adapter unless adapter.respond_to?(:new)
138
+ return adapter.new if adapter.instance_method(:initialize).arity.zero?
139
+
140
+ adapter.new(options_for(adapter_name))
141
+ end
142
+
143
+ private
144
+
145
+ def symbolize_keys(hash)
146
+ hash.each_with_object({}) do |(key, value), result|
147
+ result[key.to_sym] = value
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -3,7 +3,7 @@
3
3
  require "standup_md/config/cli"
4
4
  require "standup_md/config/file"
5
5
  require "standup_md/config/entry"
6
- require "standup_md/config/entry_list"
6
+ require "standup_md/config/post"
7
7
 
8
8
  module StandupMD
9
9
  ##
@@ -28,10 +28,10 @@ module StandupMD
28
28
  attr_reader :entry
29
29
 
30
30
  ##
31
- # Reader for EntryList config.
31
+ # Reader for Post config.
32
32
  #
33
- # @return [StandupMD::Config::EntryList]
34
- attr_reader :entry_list
33
+ # @return [StandupMD::Config::Post]
34
+ attr_reader :post
35
35
 
36
36
  ##
37
37
  # Builds the links to the configuration classes.
@@ -39,7 +39,20 @@ module StandupMD
39
39
  @cli = StandupMD::Config::Cli.new
40
40
  @file = StandupMD::Config::File.new
41
41
  @entry = StandupMD::Config::Entry.new
42
- @entry_list = StandupMD::Config::EntryList.new
42
+ @post = StandupMD::Config::Post.new
43
+ end
44
+
45
+ ##
46
+ # Builds an independent snapshot of the current configuration.
47
+ #
48
+ # @return [StandupMD::Config]
49
+ def copy
50
+ self.class.new.tap do |config|
51
+ config.cli.copy_from(cli)
52
+ config.file.copy_from(file)
53
+ config.entry.copy_from(entry)
54
+ config.post.copy_from(post)
55
+ end
43
56
  end
44
57
  end
45
58
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
3
  require "standup_md/section"
5
4
 
6
5
  module StandupMD
@@ -21,7 +20,7 @@ module StandupMD
21
20
  #
22
21
  # @return [StandupMD::Config::Entry]
23
22
  def self.config
24
- @config ||= StandupMD.config.entry
23
+ StandupMD.config.entry
25
24
  end
26
25
 
27
26
  ##
@@ -36,14 +35,33 @@ module StandupMD
36
35
  # Creates a generic entry. Default values can be set via configuration.
37
36
  # Yields the entry if a block is passed so you can change values.
38
37
  #
38
+ # @param [StandupMD::Config::Entry] config
39
+ #
40
+ # @param [Date] date
41
+ #
42
+ # @param [Array, nil] current
43
+ #
44
+ # @param [Array, nil] previous
45
+ #
46
+ # @param [Array, nil] impediments
47
+ #
48
+ # @param [Array, nil] notes
49
+ #
39
50
  # @return [StandupMD::Entry]
40
- def self.create
51
+ def self.create(
52
+ config: StandupMD.config.entry,
53
+ date: Date.today,
54
+ current: nil,
55
+ previous: nil,
56
+ impediments: nil,
57
+ notes: nil
58
+ )
41
59
  new(
42
- Date.today,
43
- config.current,
44
- config.previous,
45
- config.impediments,
46
- config.notes
60
+ date,
61
+ current || config.current,
62
+ previous || config.previous,
63
+ impediments || config.impediments,
64
+ notes || config.notes
47
65
  ).tap { |entry| yield entry if block_given? }
48
66
  end
49
67
 
@@ -62,7 +80,6 @@ module StandupMD
62
80
  def initialize(date, current, previous, impediments, notes = [])
63
81
  raise unless date.is_a?(Date)
64
82
 
65
- @config = self.class.config
66
83
  @date = date
67
84
  @sections = {}
68
85
  self.current = current
@@ -110,7 +127,7 @@ module StandupMD
110
127
  end
111
128
 
112
129
  ##
113
- # Entry as a hash .
130
+ # Entry as a hash.
114
131
  #
115
132
  # @return [Hash]
116
133
  def to_h
@@ -124,14 +141,6 @@ module StandupMD
124
141
  }
125
142
  end
126
143
 
127
- ##
128
- # Entry as a json object.
129
- #
130
- # @return [String]
131
- def to_json
132
- to_h.to_json
133
- end
134
-
135
144
  private
136
145
 
137
146
  def set_section(type, tasks)