standup_md 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ # How many entries are in the list.
60
+ #
61
+ # @return [Integer]
62
+ def size
63
+ @entries.size
64
+ end
65
+
66
+ ##
67
+ # Is the list empty?
68
+ #
69
+ # @return [Boolean] true if empty
70
+ def empty?
71
+ @entries.empty?
72
+ end
73
+
74
+ ##
75
+ # Returns a copy of self sorted by date.
76
+ #
77
+ # @return [StandupMD::EntryList]
78
+ def sort
79
+ self.class.new(*@entries.sort)
80
+ end
81
+
82
+ ##
83
+ # Replace entries with sorted entries by date.
84
+ #
85
+ # @return [StandupMD::EntryList]
86
+ def sort!
87
+ @entries = @entries.sort
88
+ self
89
+ end
90
+
91
+ ##
92
+ # Returns a copy of self sorted by date.
93
+ #
94
+ # @return [StandupMD::EntryList]
95
+ def sort_reverse
96
+ self.class.new(*@entries.sort.reverse)
97
+ end
98
+
99
+ ##
100
+ # Returns entries that are between the start and end date. This method
101
+ # assumes the list has already been sorted.
102
+ #
103
+ # @param [Date] start_date
104
+ #
105
+ # @param [Date] end_date
106
+ #
107
+ # @return [Array]
108
+ def filter(start_date, end_date)
109
+ self.class.new(
110
+ *@entries.select { |e| e.date.between?(start_date, end_date) }
111
+ )
112
+ end
113
+
114
+ ##
115
+ # Replaces entries with results of filter.
116
+ #
117
+ # @param [Date] start_date
118
+ #
119
+ # @param [Date] end_date
120
+ #
121
+ # @return [Array]
122
+ def filter!(start_date, end_date)
123
+ @entries = filter(start_date, end_date)
124
+ self
125
+ end
126
+
127
+ ##
128
+ # The first entry in the list. This method assumes the list has
129
+ # already been sorted.
130
+ #
131
+ # @return [StandupMD::Entry]
132
+ def first
133
+ to_a.first
134
+ end
135
+
136
+ ##
137
+ # The last entry in the list. This method assumes the list has
138
+ # already been sorted.
139
+ #
140
+ # @return [StandupMD::Entry]
141
+ def last
142
+ to_a.last
143
+ end
144
+
145
+ ##
146
+ # The list as a hash, with the dates as keys.
147
+ #
148
+ # @return [Hash]
149
+ def to_h
150
+ Hash[@entries.map { |e| [e.date, {
151
+ 'current' => e.current,
152
+ 'previous' => e.previous,
153
+ 'impediments' => e.impediments,
154
+ 'notes' => e.notes
155
+ }]}]
156
+ end
157
+
158
+ ##
159
+ # The entry list as a json object.
160
+ #
161
+ # @return [String]
162
+ def to_json
163
+ to_h.to_json
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,172 @@
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
+ # Find standup file in directory by file name.
24
+ #
25
+ # @param [String] File_naem
26
+ def self.find(file_name)
27
+ file = Dir.entries(config.directory).bsearch { |f| f == file_name }
28
+ if file.nil? && !config.create
29
+ raise "File #{file_name} not found." unless config.create
30
+ end
31
+ new(file_name)
32
+ end
33
+
34
+ ##
35
+ # Find standup file in directory by Date object.
36
+ #
37
+ # @param [Date] date
38
+ def self.find_by_date(date)
39
+ unless date.is_a?(Date)
40
+ raise ArgumentError, "Argument must be a Date object"
41
+ end
42
+ find(date.strftime(config.name_format))
43
+ end
44
+
45
+ ##
46
+ # The list of entries in the file.
47
+ #
48
+ # @return [StandupMP::EntryList]
49
+ attr_reader :entries
50
+
51
+ ##
52
+ # The name of the file.
53
+ #
54
+ # @return [String]
55
+ attr_reader :name
56
+
57
+ ##
58
+ # Constructs the instance.
59
+ #
60
+ # @param [String] file_name
61
+ #
62
+ # @return [StandupMP::File]
63
+ def initialize(file_name)
64
+ @config = self.class.config
65
+ if file_name.include?(::File::SEPARATOR)
66
+ raise ArgumentError,
67
+ "#{file_name} contains directory. Please use `StandupMD.config.file.directory=`"
68
+ end
69
+
70
+ unless ::File.directory?(@config.directory)
71
+ raise "Dir #{@config.directory} not found." unless @config.create
72
+ FileUtils.mkdir_p(@config.directory)
73
+ end
74
+
75
+ @name = ::File.expand_path(::File.join(@config.directory, file_name))
76
+
77
+ unless ::File.file?(@name)
78
+ raise "File #{@name} not found." unless @config.create
79
+ FileUtils.touch(@name)
80
+ end
81
+
82
+ @new = ::File.zero?(@name)
83
+ @loaded = false
84
+ end
85
+
86
+ ##
87
+ # Was the file just now created?
88
+ #
89
+ # @return [Boolean] true if new
90
+ def new?
91
+ @new
92
+ end
93
+
94
+ ##
95
+ # Has the file been loaded?
96
+ #
97
+ # @return [Boolean] true if loaded
98
+ def loaded?
99
+ @loaded
100
+ end
101
+
102
+ ##
103
+ # Does the file exist?
104
+ #
105
+ # @return [Boolean] true if exists
106
+ def exist?
107
+ ::File.exist?(name)
108
+ end
109
+
110
+ ##
111
+ # Loads the file's contents.
112
+ # TODO clean up this method.
113
+ #
114
+ # @return [StandupMD::FileList]
115
+ def load
116
+ raise "File #{name} does not exist." unless ::File.file?(name)
117
+ entry_list = EntryList.new
118
+ record = {}
119
+ section_type = ''
120
+ ::File.foreach(name) do |line|
121
+ line.chomp!
122
+ next if line.strip.empty?
123
+ if is_header?(line)
124
+ unless record.empty?
125
+ entry_list << new_entry(record)
126
+ record = {}
127
+ end
128
+ record['header'] = line.sub(%r{^\#{#{@config.header_depth}}\s*}, '')
129
+ section_type = @config.notes_header
130
+ record[section_type] = []
131
+ elsif is_sub_header?(line)
132
+ section_type = determine_section_type(line)
133
+ record[section_type] = []
134
+ else
135
+ record[section_type] << line.sub(bullet_character_regex, '')
136
+ end
137
+ end
138
+ entry_list << new_entry(record) unless record.empty?
139
+ @loaded = true
140
+ @entries = entry_list.sort
141
+ rescue => e
142
+ raise "File malformation: #{e}"
143
+ end
144
+
145
+ ##
146
+ # Writes a new entry to the file if the first entry in the file isn't today.
147
+ # This method is destructive; if a file for entries in the date range
148
+ # already exists, it will be clobbered with the entries in the range.
149
+ #
150
+ # @param [Hash] start_and_end_date
151
+ #
152
+ # @return [Boolean] true if successful
153
+ def write(dates = {})
154
+ sorted_entries = entries.sort
155
+ start_date = dates.fetch(:start_date, sorted_entries.first.date)
156
+ end_date = dates.fetch(:end_date, sorted_entries.last.date)
157
+ ::File.open(name, 'w') do |f|
158
+ sorted_entries.filter(start_date, end_date).sort_reverse.each do |entry|
159
+ f.puts header(entry.date)
160
+ @config.sub_header_order.each do |attr|
161
+ tasks = entry.send(attr)
162
+ next if !tasks || tasks.empty?
163
+ f.puts sub_header(@config.send("#{attr}_header").capitalize)
164
+ tasks.each { |task| f.puts @config.bullet_character + ' ' + task }
165
+ end
166
+ f.puts
167
+ end
168
+ end
169
+ true
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandupMD
4
+ class File
5
+
6
+ ##
7
+ # Module responsible for reading and writing standup files.
8
+ module Helpers # :nodoc:
9
+
10
+ private
11
+
12
+ def is_header?(line) # :nodoc:
13
+ line.match(header_regex)
14
+ end
15
+
16
+ def is_sub_header?(line) # :nodoc:
17
+ line.match(sub_header_regex)
18
+ end
19
+
20
+ def header_regex # :nodoc:
21
+ %r{^#{'#' * StandupMD.config.file.header_depth}\s+}
22
+ end
23
+
24
+ def sub_header_regex # :nodoc:
25
+ %r{^#{'#' * StandupMD.config.file.sub_header_depth}\s+}
26
+ end
27
+
28
+ def bullet_character_regex # :nodoc:
29
+ %r{\s*#{StandupMD.config.file.bullet_character}\s*}
30
+ end
31
+
32
+ def determine_section_type(line) # :nodoc:
33
+ line = line.sub(%r{^\#{#{StandupMD.config.file.sub_header_depth}}\s*}, '')
34
+ [
35
+ StandupMD.config.file.current_header,
36
+ StandupMD.config.file.previous_header,
37
+ StandupMD.config.file.impediments_header,
38
+ StandupMD.config.file.notes_header
39
+ ].each { |header| return header if line.include?(header) }
40
+ raise "Unrecognized header [#{line}]"
41
+ end
42
+
43
+ def new_entry(record) # :nodoc:
44
+ Entry.new(
45
+ Date.strptime(record['header'], StandupMD.config.file.header_date_format),
46
+ record[StandupMD.config.file.current_header],
47
+ record[StandupMD.config.file.previous_header],
48
+ record[StandupMD.config.file.impediments_header],
49
+ record[StandupMD.config.file.notes_header]
50
+ )
51
+ end
52
+
53
+ def header(date)
54
+ '#' * StandupMD.config.file.header_depth + ' ' + date.strftime(StandupMD.config.file.header_date_format)
55
+ end
56
+
57
+ def sub_header(sh)
58
+ '#' * StandupMD.config.file.sub_header_depth + ' ' + sh
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,9 +1,11 @@
1
- class StandupMD
1
+ # frozen_string_literal: true
2
+
3
+ module StandupMD
2
4
  ##
3
5
  # The gem verision
4
6
  #
5
7
  # @example
6
8
  # StandupMD::VERSION
7
- # # => '0.2.1'
8
- VERSION = '0.2.1'
9
+ # # => '0.3.0'
10
+ VERSION = '0.3.0'
9
11
  end
@@ -36,4 +36,5 @@ Gem::Specification.new do |spec|
36
36
  spec.require_paths = ['lib']
37
37
  spec.add_development_dependency 'rake', '~> 13.0', '>= 13.0.1'
38
38
  spec.add_development_dependency 'test-unit', '~> 3.3', '>= 3.3.5'
39
+ spec.add_development_dependency 'simplecov'
39
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standup_md
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-03 00:00:00.000000000 Z
11
+ date: 2020-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -50,6 +50,20 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 3.3.5
53
+ - !ruby/object:Gem::Dependency
54
+ name: simplecov
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
53
67
  description: Generate and edit standups in markdown format
54
68
  email: evanthegrayt@vivaldi.net
55
69
  executables:
@@ -69,6 +83,15 @@ files:
69
83
  - doc/README_md.html
70
84
  - doc/StandupMD.html
71
85
  - doc/StandupMD/Cli.html
86
+ - doc/StandupMD/Cli/Helpers.html
87
+ - doc/StandupMD/Config.html
88
+ - doc/StandupMD/Config/Cli.html
89
+ - doc/StandupMD/Config/Entry.html
90
+ - doc/StandupMD/Config/EntryList.html
91
+ - doc/StandupMD/Config/File.html
92
+ - doc/StandupMD/Entry.html
93
+ - doc/StandupMD/EntryList.html
94
+ - doc/StandupMD/File.html
72
95
  - doc/created.rid
73
96
  - doc/css/fonts.css
74
97
  - doc/css/rdoc.css
@@ -115,6 +138,16 @@ files:
115
138
  - doc/table_of_contents.html
116
139
  - lib/standup_md.rb
117
140
  - lib/standup_md/cli.rb
141
+ - lib/standup_md/cli/helpers.rb
142
+ - lib/standup_md/config.rb
143
+ - lib/standup_md/config/cli.rb
144
+ - lib/standup_md/config/entry.rb
145
+ - lib/standup_md/config/entry_list.rb
146
+ - lib/standup_md/config/file.rb
147
+ - lib/standup_md/entry.rb
148
+ - lib/standup_md/entry_list.rb
149
+ - lib/standup_md/file.rb
150
+ - lib/standup_md/file/helpers.rb
118
151
  - lib/standup_md/version.rb
119
152
  - standup_md.gemspec
120
153
  homepage: https://evanthegrayt.github.io/standup_md/