Class: Worklog::Storage

Inherits:
Object
  • Object
show all
Defined in:
lib/storage.rb

Overview

Handles storage of daily logs and people

Defined Under Namespace

Classes: LogNotFoundError

Constant Summary collapse

FILE_SUFFIX =
'.yaml'
LOG_PATTERN =

Regular expression to match daily log file names

/\d{4}-\d{2}-\d{2}#{FILE_SUFFIX}\z/
PERSON_TEMPLATE =

The template for the people YAML file. This template is used to create a new people file if it does not exist.

<<~YAML
  ---
  # Each person is defined by the following attributes:
  # - handle: <unique_handle>
  #   github_username: <github_username>
  #   name: <full_name>
  #   team: <team_name>
  #   email: <email_address>
  #   role: <role_in_team>
  #   inactive: <true_or_false>
  #   --- Define your people below this line ---
YAML

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Storage

Returns a new instance of Storage.



35
36
37
# File 'lib/storage.rb', line 35

def initialize(config)
  @config = config
end

Instance Method Details

#all_daysArray<DailyLog>

Return all logs for all available days

Returns:

  • (Array<DailyLog>)

    List of all logs



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/storage.rb', line 45

def all_days
  return [] unless folder_exists?

  logs = []
  Dir.glob(File.join(@config.storage_path, "*#{FILE_SUFFIX}")).map do |file|
    next unless file.match?(LOG_PATTERN)

    logs << load_log(file)
  end

  logs
end

#create_default_filesObject

This method assumes that the storage folder already exists. It creates default files like people.yaml if they do not exist.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/storage.rb', line 210

def create_default_files
  WorkLogger.info 'Creating default files in storage folder if they do not exist.'
  # projects_file = File.join(@config.storage_path, 'projects.yaml')
  # unless File.exist?(projects_file)
  #   File.write(projects_file, [].to_yaml)
  # end

  if File.exist?(people_filepath)
    WorkLogger.info 'people.yaml already exists, skipping creation.'
  else
    WorkLogger.info 'Creating default people.yaml file.'
    File.write(people_filepath, PERSON_TEMPLATE)
  end

  # Write the default config file if it does not exist
  return if File.exist?(Configuration.config_file_path)

  File.write(Configuration.config_file_path,
             Configuration::CONFIGURATION_TEMPLATE.result)
end

#create_default_foldervoid

This method returns an undefined value.

Create folder if not exists already.



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/storage.rb', line 196

def create_default_folder
  WorkLogger.debug 'Creating storage folder if it does not exist.'

  # Do nothing if the storage path is not the default path
  unless @config.default_storage_path?
    WorkLogger.debug 'Custom storage path detected, skipping creation of default storage folder.'
    return
  end

  Dir.mkdir(@config.storage_path) unless Dir.exist?(@config.storage_path)
end

#create_file_skeleton(date) ⇒ Object

Create file for a new day if it does not exist

Parameters:

  • date (Date)

    The date, used as the file name.



111
112
113
# File 'lib/storage.rb', line 111

def create_file_skeleton(date)
  File.write(filepath(date), YAML.dump(DailyLog.new(date:, entries: []))) unless File.exist?(filepath(date))
end

#days_between(start_date, end_date = nil, epics_only = nil, tags_filter = nil) ⇒ Array<DailyLog>

Return days between start_date and end_date If end_date is nil, return logs from start_date to today

Parameters:

  • start_date (Date)

    The start date, inclusive

  • end_date (Date) (defaults to: nil)

    The end date, inclusive

  • epics_only (Boolean) (defaults to: nil)

    If true, only return logs with epic entries

  • tags_filter (Array<String>) (defaults to: nil)

    If provided, only return logs with entries that have at least one of the tags

Returns:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/storage.rb', line 83

def days_between(start_date, end_date = nil, epics_only = nil, tags_filter = nil)
  return [] unless folder_exists?

  logs = []
  end_date = Date.today if end_date.nil?

  return [] if start_date > end_date

  while start_date <= end_date
    if File.exist?(filepath(start_date))
      tmp_logs = load_log!(filepath(start_date))
      tmp_logs.entries.keep_if { |entry| entry.epic? } if epics_only

      if tags_filter
        # Safeguard against entries without any tags, not just empty array
        tmp_logs.entries.keep_if { |entry| entry.tags&.intersect?(tags_filter) }
      end

      logs << tmp_logs if tmp_logs.entries.length > 0
    end

    start_date += 1
  end
  logs
end

#filepath(date) ⇒ String

Construct filepath for a given date.

Parameters:

  • date (Date)

    The date

Returns:

  • (String)

    The filepath



234
235
236
# File 'lib/storage.rb', line 234

def filepath(date)
  File.join(@config.storage_path, "#{date}#{FILE_SUFFIX}")
end

#folder_exists?Boolean

Returns:

  • (Boolean)


39
40
41
# File 'lib/storage.rb', line 39

def folder_exists?
  Dir.exist?(@config.storage_path)
end

#load_log(file) ⇒ Object



115
116
117
118
119
120
# File 'lib/storage.rb', line 115

def load_log(file)
  load_log!(file)
rescue LogNotFoundError
  WorkLogger.error "No work log found for #{file}. Aborting."
  nil
end

#load_log!(file) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/storage.rb', line 122

def load_log!(file)
  WorkLogger.debug "Loading file #{file}"
  begin
    yaml_content = File.read(file)
    cleaned_yaml = yaml_content.gsub(%r{!ruby/object:[^\s]+}, '')
    log = DailyLog.from_hash(YAML.safe_load(cleaned_yaml, permitted_classes: [Date, Time], symbolize_names: true))

    log.entries.each do |entry|
      entry.time = Time.parse(entry.time) unless entry.time.respond_to?(:strftime)
    end
    log
  rescue Errno::ENOENT
    raise LogNotFoundError
  end
end

#load_peopleArray<Person>

Load all people from the people file, or return an empty array if the file does not exist

Returns:

  • (Array<Person>)

    List of people



159
160
161
162
163
164
# File 'lib/storage.rb', line 159

def load_people
  load_people!
rescue Errno::ENOENT
  WorkLogger.info 'Unable to load people.'
  []
end

#load_people!Array<Person>

Load all people from the people file

Returns:

  • (Array<Person>)

    List of people



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/storage.rb', line 174

def load_people!
  return [] unless File.exist?(people_filepath)

  yamltext = File.read(people_filepath)
  if yamltext != yamltext.gsub(/^- !.*$/, '-')
    WorkLogger.debug 'The people.yaml file contains deprecated syntax. Migrating now.'
    yamltext.gsub!(/^- !.*$/, '-')
    File.write(people_filepath, yamltext)
  end
  YAML.load(yamltext, permitted_classes: []).map { |person_hash| Person.from_hash(person_hash) }
end

#load_people_hashHash<String, Person>

Load all people from the people file and return them as a hash with handle as key

Returns:

  • (Hash<String, Person>)

    Hash of people with handle as key



168
169
170
# File 'lib/storage.rb', line 168

def load_people_hash
  load_people.to_h { |person| [person.handle, person] }
end

#load_single_log_file(file, headline = true) ⇒ Object

Load a single log file and return its entries



150
151
152
153
154
# File 'lib/storage.rb', line 150

def load_single_log_file(file, headline = true)
  daily_log = load_log!(file)
  puts "Work log for #{Rainbow(daily_log.date).gold}:" if headline
  daily_log.entries
end

#people_filepathString

Return the full absolute filepath for the people.yaml file

Returns:

  • (String)

    The filepath



240
241
242
# File 'lib/storage.rb', line 240

def people_filepath
  File.join(@config.storage_path, 'people.yaml')
end

#tagsSet<String>

Return all tags as a set

Returns:

  • (Set<String>)

    Set of all tags



60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/storage.rb', line 60

def tags
  logs = all_days
  tags = Set[]
  logs.each do |log|
    log.entries.each do |entry|
      next unless entry.tags

      entry.tags.each do |tag|
        tags << tag
      end
    end
  end
  tags
end

#write_log(file, daily_log) ⇒ Object



138
139
140
141
142
143
144
145
146
147
# File 'lib/storage.rb', line 138

def write_log(file, daily_log)
  WorkLogger.debug "Writing to file #{file}"

  File.open(file, 'w') do |f|
    # Sort entries by time before saving
    daily_log.entries.sort_by!(&:time)

    f.puts daily_log.to_yaml
  end
end

#write_people!(people) ⇒ Object

Write people to the people file

Parameters:

  • people (Array<Person>)

    List of people



188
189
190
191
192
# File 'lib/storage.rb', line 188

def write_people!(people)
  File.open(people_filepath, 'w') do |f|
    f.puts people.to_yaml
  end
end