standup_md 0.3.17 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f155bb0cdeacfcf00716bfd5cfbc67a9e5d6d1d8be2b89c35d0f148ec782b264
4
- data.tar.gz: fa9f7a3d971f45f79f2f819fcaa05c761bdf2cd3d9b4de3abc942b320ca4765d
3
+ metadata.gz: 74c06f0aa4db099f1e64131fdd7e184dd8270db7ee99dd97f9c2cd97374ccef1
4
+ data.tar.gz: 71e038fce40a7f972b058baee23a7cfbbc1b2eabdbb8f0ccee466ea6fdf70345
5
5
  SHA512:
6
- metadata.gz: b22a137af1ae2a03cead81ffce5b46f517721dd22f5364318e595ebe7e22d6b9eb7e2ed05021acaec9a3f5d0f76769c98d73a898ee908be58656c4d7f01a60f8
7
- data.tar.gz: 5cbbf132c3cbec6e1b6290c1d1b13f8c7f8f25aa6b02d0e51052964b4688dcad7350488cc17810b1f87a9098bdcba21824e752c9fd7bbc71e21d3edeb86ab112
6
+ metadata.gz: 6321c1cf53d23f8a2cd05f191f87059bc9ac308951373ab391ef53c844289098b1edcd4fcb6ef107d7f5bea9f124f7cbd105d75c3d40c2f3fe689427be783264
7
+ data.tar.gz: 8cefeb0ac1a75a8313ebdaabed8edfdf5b928b73c625c3f3bafebe1b330fafe6d44de2ebc26aee9379ce811b376eac601edd23dc4870c6fafc9b5acc0babf1aa
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- standup_md (0.3.17)
4
+ standup_md (1.0.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -115,7 +115,7 @@ CHECKSUMS
115
115
  standard (1.54.0) sha256=7a4b08f83d9893083c8f03bc486f0feeb6a84d48233b40829c03ef4767ea0100
116
116
  standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b
117
117
  standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2
118
- standup_md (0.3.17)
118
+ standup_md (1.0.1)
119
119
  stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
120
120
  test-unit (3.7.8) sha256=689d1ca53f4d46f678b4e5d68d8e4294f87fc5e8b9238cc4de7c5727e8d65943
121
121
  tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # The Standup Doctor
2
2
  <!-- vi: set ts=4 sw=4 : -->
3
3
  [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fevanthegrayt%2Fstandup_md%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/evanthegrayt/standup_md/goto?ref=master)
4
- [![Gem Version](https://badge.fury.io/rb/standup_md.svg)](https://badge.fury.io/rb/standup_md)
4
+ [![Gem Version](https://img.shields.io/gem/v/standup_md.svg)](https://rubygems.org/gems/standup_md)
5
+ ![Language: Ruby](https://img.shields.io/static/v1?label=language&message=Ruby&color=CC342D&style=flat&logo=ruby&logoColor=CC342D)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
 
7
8
  > The cure for all your standup woes.
@@ -151,16 +152,7 @@ You'll notice, a lot of settings don't have the ability to be changed at runtime
151
152
  when calling the executable. This is because the file structure is very
152
153
  important, and changing values that affect formatting will cause problems with
153
154
  the file parser. If you don't want to use a default, make the change in your
154
- config file before you start editing standups. There is an [open
155
- issue](https://github.com/evanthegrayt/standup_md/issues/16) for handling this
156
- for the user, but they're not available yet.
157
-
158
- There are no options to change the headers at runtime because it uses the
159
- headers to detect tasks from previous entries. If changed at runtime, this would
160
- cause errors. For this reason, if you don't like the default headers, change
161
- them in your configuration file after installation, and then try to not change
162
- them again.
163
-
155
+ config file before you start editing standups.
164
156
 
165
157
  #### Available Config File Options and Defaults
166
158
  For command-line usage, this file needs to be named `~/.standuprc`. To use in a
@@ -180,6 +172,7 @@ StandupMD.configure do |c|
180
172
  c.file.sub_header_order = %w[previous current impediments notes]
181
173
  c.file.directory = ::File.join(ENV["HOME"], ".cache", "standup_md")
182
174
  c.file.bullet_character = "-"
175
+ c.file.indent_width = 2
183
176
  c.file.name_format = "%Y_%m.md"
184
177
  c.file.create = true
185
178
 
@@ -214,10 +207,11 @@ Some of these options can be changed at runtime. They are as follows.
214
207
 
215
208
  ```
216
209
  --current ARRAY List of current entry's tasks
217
- --previous ARRAY List of precious entry's tasks
210
+ --previous ARRAY List of previous entry's tasks
218
211
  --impediments ARRAY List of impediments for current entry
219
212
  --notes ARRAY List of notes for current entry
220
213
  --sub-header-order ARRAY The order of the sub-headers when writing the file
214
+ --indent-width INTEGER Number of spaces used for each nested task level
221
215
  -f, --file-name-format STRING Date-formattable string to use for standup file name
222
216
  -E, --editor EDITOR Editor to use for opening standup files
223
217
  -d, --directory DIRECTORY The directories where standup files are located
@@ -275,6 +269,7 @@ StandupMD.configure do |c|
275
269
  c.file.previous_header = "Yesterday"
276
270
  c.file.impediments_header = "Hold-ups"
277
271
  c.file.bullet_character = "*"
272
+ c.file.indent_width = 2
278
273
  c.file.header_date_format = "%m/%d/%Y"
279
274
  c.file.sub_header_order = %w[current previous impediments notes]
280
275
  end
@@ -62,6 +62,7 @@ _arguments -s -S \
62
62
  '--impediments[List of impediments for current entry]:impediments:_standup_array_values' \
63
63
  '--notes[List of notes for current entry]:notes:_standup_array_values' \
64
64
  '--sub-header-order[The order of the sub-headers when writing the file]:sub-header order:_standup_sub_header_order' \
65
+ '--indent-width[Number of spaces used for each nested task level]:indent width:' \
65
66
  '(-f --file-name-format)-f[Date-formattable string to use for standup file name]:strftime format:' \
66
67
  '(-f --file-name-format)--file-name-format[Date-formattable string to use for standup file name]:strftime format:' \
67
68
  '(-E --editor)-E[Editor to use for opening standup files]:editor:_path_commands' \
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "standup_md/parsers/markdown"
4
+
3
5
  module StandupMD
4
6
  class Cli
5
7
  ##
@@ -16,11 +18,13 @@ module StandupMD
16
18
 
17
19
  puts header(entry)
18
20
  config.file.sub_header_order.each do |header_type|
19
- tasks = entry.public_send(header_type)
20
- next if !tasks || tasks.empty?
21
+ tasks = entry.public_send("#{header_type}_tasks")
22
+ next if tasks.empty?
21
23
 
22
24
  puts sub_header(header_type)
23
- tasks.each { |task| puts "#{config.file.bullet_character} #{task}" }
25
+ tasks.each do |task|
26
+ puts parser.task_line(task)
27
+ end
24
28
  end
25
29
  puts
26
30
  end
@@ -51,7 +55,7 @@ module StandupMD
51
55
 
52
56
  opts.on(
53
57
  "--previous ARRAY", Array,
54
- "List of precious entry's tasks"
58
+ "List of previous entry's tasks"
55
59
  ) { |v| config.entry.previous = v }
56
60
 
57
61
  opts.on(
@@ -69,6 +73,11 @@ module StandupMD
69
73
  "The order of the sub-headers when writing the file"
70
74
  ) { |v| config.file.sub_header_order = v }
71
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
+
72
81
  opts.on(
73
82
  "-f", "--file-name-format STRING",
74
83
  "Date-formattable string to use for standup file name"
@@ -151,14 +160,14 @@ module StandupMD
151
160
  end
152
161
 
153
162
  ##
154
- # The "previous" entries.
163
+ # The "previous" tasks.
155
164
  #
156
165
  # @return [Array]
157
166
  def previous_entry(file)
158
- return config.entry.previous_entry unless config.cli.auto_fill_previous
159
- return prev_entry(prev_file.load.entries) if file.new? && prev_file
167
+ return config.entry.previous unless config.cli.auto_fill_previous
168
+ return prev_entry_tasks(prev_file.load.entries) if file.new? && prev_file
160
169
 
161
- prev_entry(file.entries)
170
+ prev_entry_tasks(file.entries)
162
171
  end
163
172
 
164
173
  ##
@@ -181,13 +190,13 @@ module StandupMD
181
190
  end
182
191
 
183
192
  ##
184
- # The previous entry.
193
+ # The previous entry's current tasks.
185
194
  #
186
195
  # @param [StandupMD::EntryList] entries
187
196
  #
188
- # @return [StandupMD::Entry]
189
- def prev_entry(entries)
190
- entries.empty? ? [] : entries.last.current
197
+ # @return [Array<StandupMD::Task>]
198
+ def prev_entry_tasks(entries)
199
+ entries.empty? ? [] : entries.last.current_tasks
191
200
  end
192
201
 
193
202
  ##
@@ -217,7 +226,11 @@ module StandupMD
217
226
  # @return [String]
218
227
  def sub_header(header_type)
219
228
  "#" * config.file.sub_header_depth + " " +
220
- config.file.public_send("#{header_type}_header").capitalize
229
+ config.file.public_send("#{header_type}_header")
230
+ end
231
+
232
+ def parser
233
+ StandupMD::Parsers::Markdown.new(config.file)
221
234
  end
222
235
  end
223
236
  end
@@ -9,6 +9,10 @@ module StandupMD
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
@@ -20,6 +20,7 @@ module StandupMD
20
20
  sub_header_order: %w[previous current impediments notes],
21
21
  directory: ::File.join(ENV["HOME"], ".cache", "standup_md"),
22
22
  bullet_character: "-",
23
+ indent_width: 2,
23
24
  name_format: "%Y_%m.md",
24
25
  create: true
25
26
  }.freeze
@@ -56,6 +57,14 @@ module StandupMD
56
57
  # @default "-" (dash)
57
58
  attr_reader :bullet_character
58
59
 
60
+ ##
61
+ # Number of spaces used for each nested task level.
62
+ #
63
+ # @return [Integer]
64
+ #
65
+ # @default 2
66
+ attr_reader :indent_width
67
+
59
68
  ##
60
69
  # String to be used as "Current" header.
61
70
  #
@@ -186,6 +195,18 @@ module StandupMD
186
195
  @bullet_character = char
187
196
  end
188
197
 
198
+ ##
199
+ # Setter for indent_width. Must be a positive integer.
200
+ #
201
+ # @param [Integer] width
202
+ #
203
+ # @return [Integer]
204
+ def indent_width=(width)
205
+ raise "Indent width must be a positive integer" unless width.is_a?(Integer) && width.positive?
206
+
207
+ @indent_width = width
208
+ end
209
+
189
210
  ##
190
211
  # Setter for directory. Must be expanded in case the user uses `~` for
191
212
  # home. If the directory doesn't exist, it will be created. To reset
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "standup_md/section"
4
5
 
5
6
  module StandupMD
6
7
  ##
@@ -9,6 +10,12 @@ module StandupMD
9
10
  class Entry
10
11
  include Comparable
11
12
 
13
+ ##
14
+ # Section identifiers supported by each standup entry.
15
+ #
16
+ # @return [Array<Symbol>]
17
+ SECTION_TYPES = %i[current previous impediments notes].freeze
18
+
12
19
  ##
13
20
  # Access to the class's configuration.
14
21
  #
@@ -25,30 +32,6 @@ module StandupMD
25
32
  # @return [Date]
26
33
  attr_accessor :date
27
34
 
28
- ##
29
- # The tasks for today.
30
- #
31
- # @return [Array]
32
- attr_accessor :current
33
-
34
- ##
35
- # The tasks from the previous day.
36
- #
37
- # @return [Array]
38
- attr_accessor :previous
39
-
40
- ##
41
- # Impediments for this entry.
42
- #
43
- # @return [Array]
44
- attr_accessor :impediments
45
-
46
- ##
47
- # Nnotes to add to this entry.
48
- #
49
- # @return [Array]
50
- attr_accessor :notes
51
-
52
35
  ##
53
36
  # Creates a generic entry. Default values can be set via configuration.
54
37
  # Yields the entry if a block is passed so you can change values.
@@ -81,10 +64,43 @@ module StandupMD
81
64
 
82
65
  @config = self.class.config
83
66
  @date = date
84
- @current = current
85
- @previous = previous
86
- @impediments = impediments
87
- @notes = notes
67
+ @sections = {}
68
+ self.current = current
69
+ self.previous = previous
70
+ self.impediments = impediments
71
+ self.notes = notes
72
+ end
73
+
74
+ SECTION_TYPES.each do |type|
75
+ define_method(type) do
76
+ section(type).tasks.map(&:to_s)
77
+ end
78
+
79
+ define_method("#{type}=") do |tasks|
80
+ set_section(type, tasks)
81
+ end
82
+
83
+ define_method("#{type}_tasks") do
84
+ section(type).tasks
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Sections for this entry.
90
+ #
91
+ # @return [Array<StandupMD::Section>]
92
+ def sections
93
+ SECTION_TYPES.map { |type| section(type) }
94
+ end
95
+
96
+ ##
97
+ # Find a section by type.
98
+ #
99
+ # @param [Symbol, String] type
100
+ #
101
+ # @return [StandupMD::Section]
102
+ def section(type)
103
+ @sections[type.to_sym] ||= Section.new(type)
88
104
  end
89
105
 
90
106
  ##
@@ -115,5 +131,11 @@ module StandupMD
115
131
  def to_json
116
132
  to_h.to_json
117
133
  end
134
+
135
+ private
136
+
137
+ def set_section(type, tasks)
138
+ @sections[type.to_sym] = Section.new(type, tasks || [])
139
+ end
118
140
  end
119
141
  end
@@ -2,14 +2,12 @@
2
2
 
3
3
  require "date"
4
4
  require "fileutils"
5
- require "standup_md/file/helpers"
5
+ require "standup_md/parsers/markdown"
6
6
 
7
7
  module StandupMD
8
8
  ##
9
9
  # Class for handling reading and writing standup files.
10
10
  class File
11
- include StandupMD::File::Helpers
12
-
13
11
  class << self
14
12
  ##
15
13
  # Access to the class's configuration.
@@ -88,6 +86,7 @@ module StandupMD
88
86
  # @return [StandupMP::File]
89
87
  def initialize(file_name)
90
88
  @config = self.class.config
89
+ @parser = StandupMD::Parsers::Markdown.new(@config)
91
90
  if file_name.include?(::File::SEPARATOR)
92
91
  raise ArgumentError,
93
92
  "#{file_name} contains directory. Please use `StandupMD.config.file.directory=`"
@@ -137,40 +136,14 @@ module StandupMD
137
136
 
138
137
  ##
139
138
  # Loads the file's contents.
140
- # TODO clean up this method.
141
139
  #
142
140
  # @return [StandupMD::FileList]
143
141
  def load
144
142
  raise "File #{name} does not exist." unless ::File.file?(name)
145
143
 
146
- entry_list = EntryList.new
147
- record = {}
148
- section_type = ""
149
- ::File.foreach(name) do |line|
150
- line.chomp!
151
- next if line.strip.empty?
152
-
153
- if header?(line)
154
- unless record.empty?
155
- entry_list << new_entry(record)
156
- record = {}
157
- end
158
- record["header"] = line.sub(/^\#{#{@config.header_depth}}\s*/, "")
159
- section_type = @config.notes_header
160
- record[section_type] = []
161
- elsif sub_header?(line)
162
- section_type = determine_section_type(line)
163
- record[section_type] = []
164
- else
165
- record[section_type] << line.sub(bullet_character_regex, "")
166
- end
167
- end
168
- entry_list << new_entry(record) unless record.empty?
169
144
  @loaded = true
170
- @entries = entry_list.sort
145
+ @entries = @parser.read(name)
171
146
  self
172
- rescue => e
173
- raise "File malformation: #{e}"
174
147
  end
175
148
 
176
149
  ##
@@ -185,20 +158,7 @@ module StandupMD
185
158
  sorted_entries = entries.sort
186
159
  start_date = dates.fetch(:start_date, sorted_entries.first.date)
187
160
  end_date = dates.fetch(:end_date, sorted_entries.last.date)
188
- ::File.open(name, "w") do |f|
189
- sorted_entries.filter(start_date, end_date).sort_reverse.each do |entry|
190
- f.puts header(entry.date)
191
- @config.sub_header_order.each do |attr|
192
- tasks = entry.public_send(attr)
193
- next if !tasks || tasks.empty?
194
-
195
- f.puts sub_header(@config.public_send("#{attr}_header").capitalize)
196
- tasks.each { |task| f.puts "#{@config.bullet_character} #{task}" }
197
- end
198
- f.puts
199
- end
200
- end
201
- true
161
+ @parser.write(name, sorted_entries, start_date: start_date, end_date: end_date)
202
162
  end
203
163
  end
204
164
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "standup_md/entry"
5
+ require "standup_md/entry_list"
6
+ require "standup_md/section"
7
+ require "standup_md/task"
8
+ require "standup_md/title"
9
+
10
+ module StandupMD
11
+ ##
12
+ # Namespace for standup file parsers and renderers.
13
+ module Parsers
14
+ ##
15
+ # Parser and renderer for the markdown standup format.
16
+ class Markdown
17
+ ##
18
+ # Access to file configuration.
19
+ #
20
+ # @return [StandupMD::Config::File]
21
+ attr_reader :config
22
+
23
+ ##
24
+ # Constructs an instance of +StandupMD::Parsers::Markdown+.
25
+ #
26
+ # @param [StandupMD::Config::File] config
27
+ def initialize(config = StandupMD.config.file)
28
+ @config = config
29
+ end
30
+
31
+ ##
32
+ # Reads entries from a markdown standup file.
33
+ #
34
+ # @param [String] file_name
35
+ #
36
+ # @return [StandupMD::EntryList]
37
+ def read(file_name)
38
+ entry_list = EntryList.new
39
+ record = nil
40
+ section = nil
41
+
42
+ ::File.foreach(file_name) do |line|
43
+ line.chomp!
44
+ next if line.strip.empty?
45
+
46
+ if header?(line)
47
+ entry_list << entry(record) if record
48
+ record = {title: title(line), sections: {}}
49
+ section = section(:notes)
50
+ record[:sections][:notes] = section
51
+ elsif sub_header?(line)
52
+ section = section(section_type(line))
53
+ record[:sections][section.type] = section
54
+ else
55
+ section << task(line)
56
+ end
57
+ end
58
+
59
+ entry_list << entry(record) if record
60
+ entry_list.sort
61
+ rescue => e
62
+ raise "File malformation: #{e}"
63
+ end
64
+
65
+ ##
66
+ # Writes entries to a markdown standup file.
67
+ #
68
+ # @param [String] file_name
69
+ # @param [StandupMD::EntryList] entries
70
+ # @param [Date] start_date
71
+ # @param [Date] end_date
72
+ #
73
+ # @return [Boolean]
74
+ def write(file_name, entries, start_date:, end_date:)
75
+ ::File.open(file_name, "w") do |f|
76
+ entries.filter(start_date, end_date).sort_reverse.each do |entry|
77
+ f.puts Title.new(entry.date).to_markdown
78
+ config.sub_header_order.each do |attr|
79
+ section = Section.new(attr, entry.public_send("#{attr}_tasks"))
80
+ next if section.empty?
81
+
82
+ f.puts section.to_markdown
83
+ end
84
+ f.puts
85
+ end
86
+ end
87
+ true
88
+ end
89
+
90
+ ##
91
+ # Renders a task as a markdown list item.
92
+ #
93
+ # @param [String, StandupMD::Task] task
94
+ #
95
+ # @return [String]
96
+ def task_line(task)
97
+ build_task(task).to_markdown
98
+ end
99
+
100
+ private
101
+
102
+ def header?(line)
103
+ line.match?(header_regex)
104
+ end
105
+
106
+ def sub_header?(line)
107
+ line.match?(sub_header_regex)
108
+ end
109
+
110
+ def header_regex
111
+ /^#{"#" * config.header_depth}\s+/
112
+ end
113
+
114
+ def sub_header_regex
115
+ /^#{"#" * config.sub_header_depth}\s+/
116
+ end
117
+
118
+ def title(line)
119
+ line.sub(/^\#{#{config.header_depth}}\s*/, "")
120
+ end
121
+
122
+ def section(type)
123
+ Section.new(type)
124
+ end
125
+
126
+ def section_type(line)
127
+ sub_header = line.sub(/^\#{#{config.sub_header_depth}}\s*/, "")
128
+ Entry::SECTION_TYPES.each do |type|
129
+ header = config.public_send("#{type}_header")
130
+ return type if sub_header.include?(header)
131
+ end
132
+ raise "Unrecognized header [#{sub_header}]"
133
+ end
134
+
135
+ def task(line)
136
+ match = line.match(task_regex)
137
+ return Task.new(line) unless match
138
+
139
+ Task.new(
140
+ match[:text],
141
+ indent_level: match[:indent].size / config.indent_width
142
+ )
143
+ end
144
+
145
+ def task_regex
146
+ /\A(?<indent>\s*)#{Regexp.escape(config.bullet_character)}\s*(?<text>.*)\z/
147
+ end
148
+
149
+ def entry(record)
150
+ Entry.new(
151
+ Date.strptime(record[:title], config.header_date_format),
152
+ tasks(record, :current),
153
+ tasks(record, :previous),
154
+ tasks(record, :impediments),
155
+ tasks(record, :notes)
156
+ )
157
+ end
158
+
159
+ def tasks(record, type)
160
+ record[:sections].fetch(type, Section.new(type)).tasks
161
+ end
162
+
163
+ def build_task(task)
164
+ return task if task.is_a?(Task)
165
+
166
+ Task.new(task)
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "standup_md/task"
4
+
5
+ module StandupMD
6
+ ##
7
+ # A named section of a standup entry, such as current, previous,
8
+ # impediments, or notes.
9
+ class Section
10
+ ##
11
+ # The semantic section type.
12
+ #
13
+ # @return [Symbol]
14
+ attr_reader :type
15
+
16
+ ##
17
+ # Tasks for the section.
18
+ #
19
+ # @return [Array<StandupMD::Task>]
20
+ attr_reader :tasks
21
+
22
+ ##
23
+ # Constructs an instance of +StandupMD::Section+.
24
+ #
25
+ # @param [Symbol, String] type
26
+ # @param [Array<String, StandupMD::Task>] tasks
27
+ def initialize(type, tasks = [])
28
+ @type = type.to_sym
29
+ @tasks = tasks.map { |task| build_task(task) }
30
+ end
31
+
32
+ ##
33
+ # Adds a task to the section.
34
+ #
35
+ # @param [String, StandupMD::Task] task
36
+ #
37
+ # @return [Array<StandupMD::Task>]
38
+ def <<(task)
39
+ tasks << build_task(task)
40
+ end
41
+
42
+ ##
43
+ # Is the section empty?
44
+ #
45
+ # @return [Boolean]
46
+ def empty?
47
+ tasks.empty?
48
+ end
49
+
50
+ ##
51
+ # The configured section heading.
52
+ #
53
+ # @return [String]
54
+ def to_s
55
+ StandupMD.config.file.public_send("#{type}_header")
56
+ end
57
+
58
+ ##
59
+ # The section rendered as markdown lines.
60
+ #
61
+ # @return [Array<String>]
62
+ def to_markdown
63
+ [
64
+ "#" * StandupMD.config.file.sub_header_depth + " " + to_s,
65
+ *tasks.map(&:to_markdown)
66
+ ]
67
+ end
68
+
69
+ private
70
+
71
+ def build_task(task)
72
+ return task if task.is_a?(Task)
73
+
74
+ Task.new(task)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandupMD
4
+ ##
5
+ # A single standup task. The text stays format-neutral, while indentation
6
+ # level lets parsers render nested tasks for their own formats.
7
+ class Task
8
+ ##
9
+ # The task text.
10
+ #
11
+ # @return [String]
12
+ attr_reader :text
13
+
14
+ ##
15
+ # The nesting level of the task.
16
+ #
17
+ # @return [Integer]
18
+ attr_reader :indent_level
19
+
20
+ ##
21
+ # Constructs an instance of +StandupMD::Task+.
22
+ #
23
+ # @param [String] text
24
+ # @param [Integer] indent_level
25
+ def initialize(text, indent_level: 0)
26
+ unless indent_level.is_a?(Integer) && !indent_level.negative?
27
+ raise ArgumentError, "Indent level must be a non-negative integer"
28
+ end
29
+
30
+ @text = text.to_s
31
+ @indent_level = indent_level
32
+ end
33
+
34
+ ##
35
+ # The format-neutral task text.
36
+ #
37
+ # @return [String]
38
+ def to_s
39
+ text
40
+ end
41
+
42
+ ##
43
+ # The task rendered as a markdown list item.
44
+ #
45
+ # @return [String]
46
+ def to_markdown
47
+ indent = " " * StandupMD.config.file.indent_width * indent_level
48
+ "#{indent}#{StandupMD.config.file.bullet_character} #{text}"
49
+ end
50
+
51
+ ##
52
+ # Compares task contents.
53
+ def ==(other)
54
+ return text == other if other.is_a?(String)
55
+ return false unless other.is_a?(Task)
56
+
57
+ text == other.text && indent_level == other.indent_level
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module StandupMD
6
+ ##
7
+ # The title for a standup entry.
8
+ class Title
9
+ ##
10
+ # The entry date.
11
+ #
12
+ # @return [Date]
13
+ attr_reader :date
14
+
15
+ ##
16
+ # Constructs an instance of +StandupMD::Title+.
17
+ #
18
+ # @param [Date] date
19
+ def initialize(date)
20
+ raise ArgumentError, "Must be a Date object" unless date.is_a?(Date)
21
+
22
+ @date = date
23
+ end
24
+
25
+ ##
26
+ # The configured title text.
27
+ #
28
+ # @return [String]
29
+ def to_s
30
+ date.strftime(StandupMD.config.file.header_date_format)
31
+ end
32
+
33
+ ##
34
+ # The title rendered as markdown.
35
+ #
36
+ # @return [String]
37
+ def to_markdown
38
+ "#" * StandupMD.config.file.header_depth + " " + to_s
39
+ end
40
+ end
41
+ end
@@ -9,19 +9,19 @@ module StandupMD
9
9
  # Major version.
10
10
  #
11
11
  # @return [Integer]
12
- MAJOR = 0
12
+ MAJOR = 1
13
13
 
14
14
  ##
15
15
  # Minor version.
16
16
  #
17
17
  # @return [Integer]
18
- MINOR = 3
18
+ MINOR = 0
19
19
 
20
20
  ##
21
21
  # Patch version.
22
22
  #
23
23
  # @return [Integer]
24
- PATCH = 17
24
+ PATCH = 1
25
25
 
26
26
  ##
27
27
  # Version as +[MAJOR, MINOR, PATCH]+
@@ -48,5 +48,9 @@ module StandupMD
48
48
  end
49
49
  end
50
50
 
51
+ ##
52
+ # Full gem version string.
53
+ #
54
+ # @return [String]
51
55
  VERSION = StandupMD::Version.to_s
52
56
  end
data/lib/standup_md.rb CHANGED
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "standup_md/version"
4
- require "standup_md/file"
4
+ require "standup_md/config"
5
+ require "standup_md/task"
6
+ require "standup_md/title"
7
+ require "standup_md/section"
5
8
  require "standup_md/entry"
6
9
  require "standup_md/entry_list"
10
+ require "standup_md/parsers/markdown"
11
+ require "standup_md/file"
7
12
  require "standup_md/cli"
8
- require "standup_md/config"
9
13
 
10
14
  ##
11
15
  # The main module for the gem. Provides access to configuration classes.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standup_md
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.17
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
@@ -119,7 +119,10 @@ files:
119
119
  - lib/standup_md/entry.rb
120
120
  - lib/standup_md/entry_list.rb
121
121
  - lib/standup_md/file.rb
122
- - lib/standup_md/file/helpers.rb
122
+ - lib/standup_md/parsers/markdown.rb
123
+ - lib/standup_md/section.rb
124
+ - lib/standup_md/task.rb
125
+ - lib/standup_md/title.rb
123
126
  - lib/standup_md/version.rb
124
127
  - standup_md.gemspec
125
128
  homepage: https://evanthegrayt.github.io/standup_md/
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StandupMD
4
- class File
5
- ##
6
- # Module responsible for reading and writing standup files.
7
- module Helpers # :nodoc:
8
- private
9
-
10
- def header?(line) # :nodoc:
11
- line.match(header_regex)
12
- end
13
-
14
- def sub_header?(line) # :nodoc:
15
- line.match(sub_header_regex)
16
- end
17
-
18
- def header_regex # :nodoc:
19
- /^#{"#" * StandupMD.config.file.header_depth}\s+/
20
- end
21
-
22
- def sub_header_regex # :nodoc:
23
- /^#{"#" * StandupMD.config.file.sub_header_depth}\s+/
24
- end
25
-
26
- def bullet_character_regex # :nodoc:
27
- /\s*#{StandupMD.config.file.bullet_character}\s*/
28
- end
29
-
30
- def determine_section_type(line) # :nodoc:
31
- line = line.sub(/^\#{#{StandupMD.config.file.sub_header_depth}}\s*/, "")
32
- [
33
- StandupMD.config.file.current_header,
34
- StandupMD.config.file.previous_header,
35
- StandupMD.config.file.impediments_header,
36
- StandupMD.config.file.notes_header
37
- ].each { |header| return header if line.include?(header) }
38
- raise "Unrecognized header [#{line}]"
39
- end
40
-
41
- def new_entry(record) # :nodoc:
42
- Entry.new(
43
- Date.strptime(
44
- record["header"],
45
- StandupMD.config.file.header_date_format
46
- ),
47
- record[StandupMD.config.file.current_header],
48
- record[StandupMD.config.file.previous_header],
49
- record[StandupMD.config.file.impediments_header],
50
- record[StandupMD.config.file.notes_header]
51
- )
52
- end
53
-
54
- def header(date)
55
- "#" * StandupMD.config.file.header_depth +
56
- " " +
57
- date.strftime(StandupMD.config.file.header_date_format)
58
- end
59
-
60
- def sub_header(subhead)
61
- "#" * StandupMD.config.file.sub_header_depth + " " + subhead
62
- end
63
- end
64
- end
65
- end