standup_md 0.2.0 → 0.3.3
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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +7 -1
- data/README.md +149 -119
- data/bin/standup +1 -1
- data/doc/README_md.html +158 -101
- data/doc/StandupMD.html +96 -1326
- data/doc/StandupMD/Cli.html +124 -479
- data/doc/StandupMD/Cli/Helpers.html +167 -0
- data/doc/StandupMD/Config.html +230 -0
- data/doc/StandupMD/Config/Cli.html +364 -0
- data/doc/StandupMD/Config/Entry.html +296 -0
- data/doc/StandupMD/Config/EntryList.html +212 -0
- data/doc/StandupMD/Config/File.html +613 -0
- data/doc/StandupMD/Entry.html +478 -0
- data/doc/StandupMD/EntryList.html +759 -0
- data/doc/StandupMD/File.html +614 -0
- data/doc/created.rid +15 -5
- data/doc/index.html +168 -99
- data/doc/js/search_index.js +1 -1
- data/doc/js/search_index.js.gz +0 -0
- data/doc/table_of_contents.html +226 -72
- data/lib/standup_md.rb +28 -546
- data/lib/standup_md/cli.rb +63 -246
- data/lib/standup_md/cli/helpers.rb +165 -0
- data/lib/standup_md/config.rb +47 -0
- data/lib/standup_md/config/cli.rb +109 -0
- data/lib/standup_md/config/entry.rb +68 -0
- data/lib/standup_md/config/entry_list.rb +31 -0
- data/lib/standup_md/config/file.rb +209 -0
- data/lib/standup_md/entry.rb +122 -0
- data/lib/standup_md/entry_list.rb +166 -0
- data/lib/standup_md/file.rb +183 -0
- data/lib/standup_md/file/helpers.rb +62 -0
- data/lib/standup_md/version.rb +5 -5
- data/standup_md.gemspec +1 -0
- metadata +35 -2
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module StandupMD
|
6
|
+
|
7
|
+
##
|
8
|
+
# Class for handling single entries. Includes the comparable module, and
|
9
|
+
# compares by date.
|
10
|
+
class Entry
|
11
|
+
include Comparable
|
12
|
+
|
13
|
+
##
|
14
|
+
# Access to the class's configuration.
|
15
|
+
#
|
16
|
+
# @return [StandupMD::Config::Entry]
|
17
|
+
def self.config
|
18
|
+
@config ||= StandupMD.config.entry
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# The date of the entry.
|
23
|
+
#
|
24
|
+
# @param [Date] date
|
25
|
+
#
|
26
|
+
# @return [Date]
|
27
|
+
attr_accessor :date
|
28
|
+
|
29
|
+
##
|
30
|
+
# The tasks for today.
|
31
|
+
#
|
32
|
+
# @return [Array]
|
33
|
+
attr_accessor :current
|
34
|
+
|
35
|
+
##
|
36
|
+
# The tasks from the previous day.
|
37
|
+
#
|
38
|
+
# @return [Array]
|
39
|
+
attr_accessor :previous
|
40
|
+
|
41
|
+
##
|
42
|
+
# Iimpediments for this entry.
|
43
|
+
#
|
44
|
+
# @return [Array]
|
45
|
+
attr_accessor :impediments
|
46
|
+
|
47
|
+
##
|
48
|
+
# Nnotes to add to this entry.
|
49
|
+
#
|
50
|
+
# @return [Array]
|
51
|
+
attr_accessor :notes
|
52
|
+
|
53
|
+
##
|
54
|
+
# Creates a generic entry. Default values can be set via configuration.
|
55
|
+
# Yields the entry if a block is passed so you can change values.
|
56
|
+
#
|
57
|
+
# @return [StandupMD::Entry]
|
58
|
+
def self.create
|
59
|
+
entry = new(
|
60
|
+
Date.today,
|
61
|
+
config.current,
|
62
|
+
config.previous,
|
63
|
+
config.impediments,
|
64
|
+
config.notes
|
65
|
+
)
|
66
|
+
yield config if block_given?
|
67
|
+
entry
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Constructs instance of +StandupMD::Entry+.
|
72
|
+
#
|
73
|
+
# @param [Date] date
|
74
|
+
#
|
75
|
+
# @param [Array] current
|
76
|
+
#
|
77
|
+
# @param [Array] previous
|
78
|
+
#
|
79
|
+
# @param [Array] impediments
|
80
|
+
#
|
81
|
+
# @param [Array] notes
|
82
|
+
def initialize(date, current, previous, impediments, notes = [])
|
83
|
+
raise unless date.is_a?(Date)
|
84
|
+
@config = self.class.config
|
85
|
+
|
86
|
+
@date = date
|
87
|
+
@current = current
|
88
|
+
@previous = previous
|
89
|
+
@impediments = impediments
|
90
|
+
@notes = notes
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Sorting method for Comparable. Entries are compared by date.
|
95
|
+
def <=>(other)
|
96
|
+
date <=> other.date
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Entry as a hash .
|
101
|
+
#
|
102
|
+
# @return [Hash]
|
103
|
+
def to_h
|
104
|
+
{
|
105
|
+
date => {
|
106
|
+
'current' => current,
|
107
|
+
'previous' => previous,
|
108
|
+
'impediments' => impediments,
|
109
|
+
'notes' => notes
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Entry as a json object.
|
116
|
+
#
|
117
|
+
# @return [String]
|
118
|
+
def to_json
|
119
|
+
to_h.to_json
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StandupMD
|
4
|
+
|
5
|
+
##
|
6
|
+
# Enumerable list of entries.
|
7
|
+
class EntryList
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
##
|
11
|
+
# Access to the class's configuration.
|
12
|
+
#
|
13
|
+
# @return [StandupMD::Config::EntryList]
|
14
|
+
def self.config
|
15
|
+
@config ||= StandupMD.config.entry_list
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Contruct a list. Can pass any amount of +StandupMD::Entry+ instances.
|
20
|
+
#
|
21
|
+
# @param [Entry] entries
|
22
|
+
#
|
23
|
+
# @return [StandupMD::EntryList]
|
24
|
+
def initialize(*entries)
|
25
|
+
@config = self.class.config
|
26
|
+
unless entries.all? { |e| e.is_a?(StandupMD::Entry) }
|
27
|
+
raise ArgumentError, 'Entry must instance of StandupMD::Entry'
|
28
|
+
end
|
29
|
+
@entries = entries
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Iterate over the list and yield each entry.
|
34
|
+
def each(&block)
|
35
|
+
@entries.each(&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Appends entries to list.
|
40
|
+
#
|
41
|
+
# @param [StandupMD::Entry] entry
|
42
|
+
#
|
43
|
+
# @return [Array]
|
44
|
+
def <<(entry)
|
45
|
+
unless entry.is_a?(StandupMD::Entry)
|
46
|
+
raise ArgumentError, 'Entry must instance of StandupMD::Entry'
|
47
|
+
end
|
48
|
+
@entries << entry
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Finds an entry based on date. This method assumes the list has already
|
53
|
+
# been sorted.
|
54
|
+
def find(key)
|
55
|
+
to_a.bsearch { |e| e.date == key }
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Returns a copy of self sorted by date.
|
60
|
+
#
|
61
|
+
# @return [StandupMD::EntryList]
|
62
|
+
def sort
|
63
|
+
self.class.new(*@entries.sort)
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Replace entries with sorted entries by date.
|
68
|
+
#
|
69
|
+
# @return [StandupMD::EntryList]
|
70
|
+
def sort!
|
71
|
+
@entries = @entries.sort
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Returns a copy of self sorted by date.
|
77
|
+
#
|
78
|
+
# @return [StandupMD::EntryList]
|
79
|
+
def sort_reverse
|
80
|
+
self.class.new(*@entries.sort.reverse)
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Returns entries that are between the start and end date. This method
|
85
|
+
# assumes the list has already been sorted.
|
86
|
+
#
|
87
|
+
# @param [Date] start_date
|
88
|
+
#
|
89
|
+
# @param [Date] end_date
|
90
|
+
#
|
91
|
+
# @return [Array]
|
92
|
+
def filter(start_date, end_date)
|
93
|
+
self.class.new(
|
94
|
+
*@entries.select { |e| e.date.between?(start_date, end_date) }
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Replaces entries with results of filter.
|
100
|
+
#
|
101
|
+
# @param [Date] start_date
|
102
|
+
#
|
103
|
+
# @param [Date] end_date
|
104
|
+
#
|
105
|
+
# @return [Array]
|
106
|
+
def filter!(start_date, end_date)
|
107
|
+
@entries = filter(start_date, end_date)
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# The list as a hash, with the dates as keys.
|
113
|
+
#
|
114
|
+
# @return [Hash]
|
115
|
+
def to_h
|
116
|
+
Hash[@entries.map { |e| [e.date, {
|
117
|
+
'current' => e.current,
|
118
|
+
'previous' => e.previous,
|
119
|
+
'impediments' => e.impediments,
|
120
|
+
'notes' => e.notes
|
121
|
+
}]}]
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# The entry list as a json object.
|
126
|
+
#
|
127
|
+
# @return [String]
|
128
|
+
def to_json
|
129
|
+
to_h.to_json
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# The first entry in the list. This method assumes the list has
|
134
|
+
# already been sorted.
|
135
|
+
#
|
136
|
+
# @return [StandupMD::Entry]
|
137
|
+
def first
|
138
|
+
to_a.first
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# The last entry in the list. This method assumes the list has
|
143
|
+
# already been sorted.
|
144
|
+
#
|
145
|
+
# @return [StandupMD::Entry]
|
146
|
+
def last
|
147
|
+
to_a.last
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# How many entries are in the list.
|
152
|
+
#
|
153
|
+
# @return [Integer]
|
154
|
+
def size
|
155
|
+
@entries.size
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Is the list empty?
|
160
|
+
#
|
161
|
+
# @return [Boolean] true if empty
|
162
|
+
def empty?
|
163
|
+
@entries.empty?
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'fileutils'
|
5
|
+
require_relative 'file/helpers'
|
6
|
+
|
7
|
+
module StandupMD
|
8
|
+
|
9
|
+
##
|
10
|
+
# Class for handling reading and writing standup files.
|
11
|
+
class File
|
12
|
+
include StandupMD::File::Helpers
|
13
|
+
|
14
|
+
##
|
15
|
+
# Access to the class's configuration.
|
16
|
+
#
|
17
|
+
# @return [StandupMD::Config::EntryList]
|
18
|
+
def self.config
|
19
|
+
@config ||= StandupMD.config.file
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Convenience method for calling File.find(file_name).load
|
24
|
+
#
|
25
|
+
# @param [String] file_name
|
26
|
+
#
|
27
|
+
# @return [StandupMD::File]
|
28
|
+
def self.load(file_name)
|
29
|
+
new(file_name).load
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Find standup file in directory by file name.
|
34
|
+
#
|
35
|
+
# @param [String] File_naem
|
36
|
+
def self.find(file_name)
|
37
|
+
file = Dir.entries(config.directory).bsearch { |f| f == file_name }
|
38
|
+
if file.nil? && !config.create
|
39
|
+
raise "File #{file_name} not found." unless config.create
|
40
|
+
end
|
41
|
+
new(file_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Find standup file in directory by Date object.
|
46
|
+
#
|
47
|
+
# @param [Date] date
|
48
|
+
def self.find_by_date(date)
|
49
|
+
unless date.is_a?(Date)
|
50
|
+
raise ArgumentError, "Argument must be a Date object"
|
51
|
+
end
|
52
|
+
find(date.strftime(config.name_format))
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# The list of entries in the file.
|
57
|
+
#
|
58
|
+
# @return [StandupMP::EntryList]
|
59
|
+
attr_reader :entries
|
60
|
+
|
61
|
+
##
|
62
|
+
# The name of the file.
|
63
|
+
#
|
64
|
+
# @return [String]
|
65
|
+
attr_reader :name
|
66
|
+
|
67
|
+
##
|
68
|
+
# Constructs the instance.
|
69
|
+
#
|
70
|
+
# @param [String] file_name
|
71
|
+
#
|
72
|
+
# @return [StandupMP::File]
|
73
|
+
def initialize(file_name)
|
74
|
+
@config = self.class.config
|
75
|
+
if file_name.include?(::File::SEPARATOR)
|
76
|
+
raise ArgumentError,
|
77
|
+
"#{file_name} contains directory. Please use `StandupMD.config.file.directory=`"
|
78
|
+
end
|
79
|
+
|
80
|
+
unless ::File.directory?(@config.directory)
|
81
|
+
raise "Dir #{@config.directory} not found." unless @config.create
|
82
|
+
FileUtils.mkdir_p(@config.directory)
|
83
|
+
end
|
84
|
+
|
85
|
+
@name = ::File.expand_path(::File.join(@config.directory, file_name))
|
86
|
+
|
87
|
+
unless ::File.file?(@name)
|
88
|
+
raise "File #{@name} not found." unless @config.create
|
89
|
+
FileUtils.touch(@name)
|
90
|
+
end
|
91
|
+
|
92
|
+
@new = ::File.zero?(@name)
|
93
|
+
@loaded = false
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Was the file just now created?
|
98
|
+
#
|
99
|
+
# @return [Boolean] true if new
|
100
|
+
def new?
|
101
|
+
@new
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Has the file been loaded?
|
106
|
+
#
|
107
|
+
# @return [Boolean] true if loaded
|
108
|
+
def loaded?
|
109
|
+
@loaded
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Does the file exist?
|
114
|
+
#
|
115
|
+
# @return [Boolean] true if exists
|
116
|
+
def exist?
|
117
|
+
::File.exist?(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Loads the file's contents.
|
122
|
+
# TODO clean up this method.
|
123
|
+
#
|
124
|
+
# @return [StandupMD::FileList]
|
125
|
+
def load
|
126
|
+
raise "File #{name} does not exist." unless ::File.file?(name)
|
127
|
+
entry_list = EntryList.new
|
128
|
+
record = {}
|
129
|
+
section_type = ''
|
130
|
+
::File.foreach(name) do |line|
|
131
|
+
line.chomp!
|
132
|
+
next if line.strip.empty?
|
133
|
+
if is_header?(line)
|
134
|
+
unless record.empty?
|
135
|
+
entry_list << new_entry(record)
|
136
|
+
record = {}
|
137
|
+
end
|
138
|
+
record['header'] = line.sub(%r{^\#{#{@config.header_depth}}\s*}, '')
|
139
|
+
section_type = @config.notes_header
|
140
|
+
record[section_type] = []
|
141
|
+
elsif is_sub_header?(line)
|
142
|
+
section_type = determine_section_type(line)
|
143
|
+
record[section_type] = []
|
144
|
+
else
|
145
|
+
record[section_type] << line.sub(bullet_character_regex, '')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
entry_list << new_entry(record) unless record.empty?
|
149
|
+
@loaded = true
|
150
|
+
@entries = entry_list.sort
|
151
|
+
self
|
152
|
+
rescue => e
|
153
|
+
raise "File malformation: #{e}"
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Writes a new entry to the file if the first entry in the file isn't today.
|
158
|
+
# This method is destructive; if a file for entries in the date range
|
159
|
+
# already exists, it will be clobbered with the entries in the range.
|
160
|
+
#
|
161
|
+
# @param [Hash] start_and_end_date
|
162
|
+
#
|
163
|
+
# @return [Boolean] true if successful
|
164
|
+
def write(dates = {})
|
165
|
+
sorted_entries = entries.sort
|
166
|
+
start_date = dates.fetch(:start_date, sorted_entries.first.date)
|
167
|
+
end_date = dates.fetch(:end_date, sorted_entries.last.date)
|
168
|
+
::File.open(name, 'w') do |f|
|
169
|
+
sorted_entries.filter(start_date, end_date).sort_reverse.each do |entry|
|
170
|
+
f.puts header(entry.date)
|
171
|
+
@config.sub_header_order.each do |attr|
|
172
|
+
tasks = entry.send(attr)
|
173
|
+
next if !tasks || tasks.empty?
|
174
|
+
f.puts sub_header(@config.send("#{attr}_header").capitalize)
|
175
|
+
tasks.each { |task| f.puts @config.bullet_character + ' ' + task }
|
176
|
+
end
|
177
|
+
f.puts
|
178
|
+
end
|
179
|
+
end
|
180
|
+
true
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|