piece_of_wax 1.0.0.rc1 → 1.0.0.rc6

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: 818b72b11bd06930c11e17c07e72512f9a4b1864dfb00090c681226414b720a3
4
- data.tar.gz: f2423eb394952af0fadd964245b4637e5e2b58267933442459ea89dbe3203640
3
+ metadata.gz: 7e3d7bf2ae9792450cc94d927669b1d48e8c4abdde8285c7580532065372780c
4
+ data.tar.gz: a2728bf010e7a3be407291ed6bd037a1c45fee19f2813e2f3d103ceae87f6649
5
5
  SHA512:
6
- metadata.gz: 105bd336b6d5d5ed58b53263fe05ed542f3b1dc35c2d346239f469d2935a9e73b2ad05249db8e984d28843b077b3a1850f5ab456825a43813b3673c48294bb51
7
- data.tar.gz: 20b656e3e4fb4bb5a53e69ee010a0bcf87845479e437c267a549855cfca70be9a12393d5787c2d507fc66526d499e47599e36a5fc44f0d9e8fa7bb05446ad709
6
+ metadata.gz: 0165421b35cf5701533a15aba5d62f324ad575686588b6858336fcc9e21171b98103019191ba67bf6c512ffa8fc39604a3bbac496f3d810ae455772faeca82a6
7
+ data.tar.gz: 64cf7f4ddb03e81c714892e0381bbc948883185bbf0124e36a7fcdfec8ee34b90a3f0168aa2bddf69a5dea35b0b9614cceb442bc6ea34a280bdba4d7e92a6e7b
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require 'piece_of_wax'
4
+ require "piece_of_wax"
4
5
  PieceOfWax::CLI.start(ARGV)
@@ -1,38 +1,111 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "piece_of_wax/cli_options"
3
- require "piece_of_wax/date_time"
5
+ require "piece_of_wax/date_time_helper"
4
6
  require "piece_of_wax/date_time_decorator/error_message_display"
5
7
  require "piece_of_wax/logfile"
6
8
  require "thor"
7
9
 
8
10
  module PieceOfWax
11
+ #
12
+ # Implements command line interface. Each public method corresponds to a `piece_of_wax`
13
+ # subcommand.
14
+ #
9
15
  class CLI < Thor
10
- desc 'activity "_activity_"', "log activity (wrap multi-word entries in quotation marks)"
11
- option :time, aliases: :t, desc: 'time when activity was started'
12
- def activity(str)
13
- CLIOptions.instance.set(options)
14
- time = DateTime.safe_parse(options[:time]) || DateTime.current_time
15
- Logfile.new(DateTime.current_date).add_activity(time, str)
16
- $stdout.puts "wrote #{str} to file"
16
+ desc 'activity "_activity_"', "log activity"
17
+ option :time, aliases: :t, desc: "time when activity was started"
18
+ def activity(*str)
19
+ capture_options(options)
20
+ time = DateTimeHelper.safe_parse(CLIOptions.instance[:time]) || DateTimeHelper.current_time
21
+ description = str.join(" ")
22
+ Logfile.new(DateTimeHelper.current_date).add_activity(time, description)
23
+ $stdout.puts "wrote #{description} to file"
17
24
  end
18
25
 
19
- desc "read [date]", "print activities on given [date] (yesterday by default)"
20
- option :military, aliases: :m, desc: 'render time in military (24-hour) format'
21
- def read(date_str = '')
22
- CLIOptions.instance.set(options)
23
- date = DateTime.safe_parse(date_str) || DateTime.current_date
24
- logfile = Logfile.new(date)
25
- if logfile.contents
26
+ desc 'edit "_activity_"', "edit activity"
27
+ option :n, default: "1", desc: "the activity to edit, 1 being the most recently-logged"
28
+ def edit(*str)
29
+ capture_options(options)
30
+ description = str.join(" ")
31
+ index = (0 - CLIOptions.instance[:n].to_i)
32
+ logfile = Logfile.new(DateTimeHelper.current_date)
33
+ if logfile.contents[index]
34
+ logfile.edit_activity(index, description)
35
+ $stdout.puts "wrote #{description} to file"
36
+ else
37
+ warn 'activity referenced by "n" option does not exist'
38
+ end
39
+ end
40
+
41
+ desc "read [date]", "print activities on given [date] (today by default)"
42
+ option :military, aliases: :m, desc: "render time in military (24-hour) format"
43
+ option(
44
+ :'quitting-time',
45
+ default: "17:45:00 -0500",
46
+ aliases: :q,
47
+ desc: "last activity period is capped off at this time"
48
+ )
49
+ def read(date_str = "")
50
+ capture_options(options)
51
+ date = get_date(date_str)
52
+ logfile = Logfile.new(date) if date
53
+ if logfile&.any_contents?
26
54
  $stdout.puts logfile
27
55
  else
28
- $stderr.puts "Sorry, I couldn't find any logs for #{DateTimeDecorator::ErrorMessageDisplay.new(date)}."
56
+ error_message(date_str, date)
29
57
  end
30
58
  end
31
59
 
32
60
  private
33
61
 
62
+ def capture_options(options)
63
+ CLIOptions.instance.set(options)
64
+ end
65
+
66
+ def get_date(date_str)
67
+ if date_str == "previous"
68
+ previous_logfile_date
69
+ else
70
+ DateTimeHelper.safe_parse(date_str) || DateTimeHelper.current_date
71
+ end
72
+ end
73
+
74
+ def error_message(date_str, date)
75
+ if date_str == "previous" && date.nil?
76
+ warn(
77
+ "Sorry, I couldn't find any logs prior to "\
78
+ "#{DateTimeDecorator::ErrorMessageDisplay.new(DateTimeHelper.current_date)}."
79
+ )
80
+ else
81
+ warn "Sorry, I couldn't find any logs for #{DateTimeDecorator::ErrorMessageDisplay.new(date)}."
82
+ end
83
+ end
84
+
34
85
  def exit_on_failure?
35
86
  true
36
87
  end
88
+
89
+ #
90
+ # @return [Logfile] most recent logfile (excluding current date)
91
+ #
92
+ # rubocop:disable Metrics/MethodLength
93
+ def previous_logfile_date
94
+ # rubocop:enable Metrics/MethodLength
95
+ logfile_dates =
96
+ Dir.entries(".").map do |filename|
97
+ date = filename.gsub(".txt", "")
98
+ DateTimeHelper.safe_parse(date)
99
+ end.compact
100
+ most_recent_date, second_most_recent_date =
101
+ logfile_dates.sort.reverse.first(2)
102
+ # if most recent logfile is from today, return the
103
+ # second most recent
104
+ if most_recent_date&.to_date == Date.today
105
+ second_most_recent_date
106
+ else
107
+ most_recent_date
108
+ end
109
+ end
37
110
  end
38
111
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "singleton"
5
+
6
+ module PieceOfWax
7
+ #
8
+ # Singleton class that stores options/flags passed from the command line. Purpose
9
+ # of the class is to make these options globally available.
10
+ #
11
+ class CLIOptions
12
+ include Singleton
13
+ extend Forwardable
14
+
15
+ def [](key)
16
+ options[key]
17
+ end
18
+
19
+ def set(opts)
20
+ raise "CLIOptions is an immutable singleton; #set can only be called once per program execution" if options.any?
21
+
22
+ @options = opts
23
+ end
24
+
25
+ private
26
+
27
+ def options
28
+ @options || {}
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "piece_of_wax/date_time"
4
+
5
+ module PieceOfWax
6
+ module DateTimeDecorator
7
+ #
8
+ # Decorator class designed to wrap DateTime instances. Implements form of to_s used
9
+ # to render error messages.
10
+ #
11
+ class ErrorMessageDisplay
12
+ def initialize(date_time)
13
+ @date_time = date_time
14
+ end
15
+
16
+ def to_s
17
+ case @date_time.to_date
18
+ when DateTimeHelper.current_date
19
+ "today"
20
+ when DateTimeHelper.current_date.prev_day
21
+ "yesterday"
22
+ else
23
+ @date_time.strftime("%Y-%-m-%-d")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PieceOfWax
4
+ module DateTimeDecorator
5
+ #
6
+ # Decorator class designed to wrap DateTime instances. Implements form of to_s used
7
+ # to render times in output of `read` subcommand.
8
+ #
9
+ class ReadDisplay
10
+ def initialize(date_time)
11
+ @date_time = date_time
12
+ end
13
+
14
+ def to_s
15
+ if CLIOptions.instance[:military]
16
+ @date_time.strftime("%H:%M")
17
+ else
18
+ @date_time.strftime("%I:%M %p")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "piece_of_wax/cli_options"
4
+ require "time"
5
+
6
+ module PieceOfWax
7
+ #
8
+ # Implements helper methods related to Date/Time
9
+ #
10
+ module DateTimeHelper
11
+ def self.safe_parse(arg)
12
+ # NOTE: calling to_s so method is safe for non-string args
13
+ str = arg.to_s
14
+ begin
15
+ Time.parse(str)
16
+ rescue ArgumentError
17
+ nil
18
+ end
19
+ end
20
+
21
+ def self.current_date
22
+ # NOTE: Possible that current_date returns two different values over course of
23
+ # same piece_of_wax invocation (if run at midnight.) I consider this
24
+ # to be a major edge case with minimal consequences, so I'm not
25
+ # going to the trouble of memoizing at this point. - Ben S.
26
+ Time.now.to_date
27
+ end
28
+
29
+ def self.current_time
30
+ Time.now
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PieceOfWax
4
+ #
5
+ # A FileIO instance represents a file. Abstracts reading from and writing to the file system.
6
+ #
7
+ class FileIO
8
+ def initialize(filename)
9
+ @filename = filename
10
+ end
11
+
12
+ def read
13
+ File.read(@filename)
14
+ rescue Errno::ENOENT
15
+ nil
16
+ end
17
+
18
+ def overwrite_with(str)
19
+ File.open(@filename, "w") do |f|
20
+ f.write(str)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,32 +1,44 @@
1
- require 'erb'
2
- require 'json'
3
- require 'piece_of_wax/file_io'
4
- require 'piece_of_wax/output_line/activity'
5
- require 'piece_of_wax/output_line/date'
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "json"
5
+ require "piece_of_wax/file_io"
6
+ require "piece_of_wax/output_line/activity"
7
+ require "piece_of_wax/output_line/date"
6
8
 
7
9
  module PieceOfWax
10
+ #
11
+ # A Logfile instance represents a specific type of file used by piece_of_wax to
12
+ # store logged time (a "time log.") A time log stores information for a single day,
13
+ # which is why a date must be passed to the Logfile constructor.
14
+ #
8
15
  class Logfile
9
16
  def initialize(date)
10
- filename = "%s.txt" % date.strftime("%y%m%d")
17
+ filename = format("%s.txt", date.strftime("%y%m%d"))
11
18
  @file_io = FileIO.new(filename)
12
19
  end
13
20
 
14
21
  def contents
15
- return unless raw_content = @file_io.read
22
+ return [] unless (raw_content = @file_io.read)
23
+
16
24
  JSON.parse(raw_content)
17
25
  end
18
26
 
27
+ def any_contents?
28
+ contents.any?
29
+ end
30
+
19
31
  def add_activity(time, description)
20
- data =
21
- { datetime: time, description: description }
22
- obj =
23
- if contents
24
- contents.insert(proper_index(time), data)
25
- else
26
- [data]
27
- end
28
- str = obj.to_json
29
- @file_io.overwrite_with(str)
32
+ data = { datetime: time, description: description }
33
+ index = proper_index(time)
34
+ json_str = contents.insert(index, data).to_json
35
+ @file_io.overwrite_with(json_str)
36
+ end
37
+
38
+ def edit_activity(index, description)
39
+ updated_contents = contents.tap { |c| c[index]["description"] = description }
40
+ json_str = updated_contents.to_json
41
+ @file_io.overwrite_with(json_str)
30
42
  end
31
43
 
32
44
  def to_s
@@ -36,7 +48,7 @@ module PieceOfWax
36
48
  <% contents.each_cons(2).map do |line1, line2| %>
37
49
  <%= OutputLine::Activity.new(line1['datetime'], line2['datetime'], line1['description']) %>
38
50
  <% end.join("\n") %>
39
- <%= OutputLine::Activity.new(last_entry['datetime'], "17:45:00 -0500", last_entry['description']) %>
51
+ <%= OutputLine::Activity.new(last_entry['datetime'], quitting_time, last_entry['description']) %>
40
52
  ERB
41
53
  # Unsure why ERB is inserting the double linebreaks after each
42
54
  # iteration of `contents.each_cons(2)`. For now, subbing single linebreak
@@ -48,9 +60,9 @@ module PieceOfWax
48
60
 
49
61
  def proper_index(time)
50
62
  contents.each_with_index do |line, i|
51
- return i if Time.parse(line['datetime']) > time
63
+ return i if Time.parse(line["datetime"]) > time
52
64
  end
53
- return contents.length
65
+ contents.length
54
66
  end
55
67
 
56
68
  def first_entry
@@ -60,5 +72,9 @@ module PieceOfWax
60
72
  def last_entry
61
73
  contents.last
62
74
  end
75
+
76
+ def quitting_time
77
+ CLIOptions.instance[:'quitting-time']
78
+ end
63
79
  end
64
80
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "piece_of_wax/date_time_helper"
4
+ require "piece_of_wax/date_time_decorator/read_display"
5
+
6
+ module PieceOfWax
7
+ module OutputLine
8
+ #
9
+ # An Activity instance represents a single logged activity, rendered as a line of
10
+ # `read` subcommand output.
11
+ #
12
+ class Activity
13
+ def initialize(start_time_str, end_time_str, description)
14
+ @start_time = DateTimeDecorator::ReadDisplay.new(DateTimeHelper.safe_parse(start_time_str))
15
+ @end_time = DateTimeDecorator::ReadDisplay.new(DateTimeHelper.safe_parse(end_time_str))
16
+ @description = description
17
+ end
18
+
19
+ def to_s
20
+ "#{@start_time}-#{@end_time} - #{@description}"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "piece_of_wax/date_time_helper"
4
+
5
+ module PieceOfWax
6
+ module OutputLine
7
+ #
8
+ # A Date instance represents the date of a time log (see Logfile,) rendered as a line of
9
+ # `read` subcommand output.
10
+ #
11
+ class Date
12
+ def initialize(datetime_str)
13
+ @date = DateTimeHelper.safe_parse(datetime_str)
14
+ end
15
+
16
+ def to_s
17
+ @date.strftime("%-m/%-d/%y")
18
+ end
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: piece_of_wax
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc1
4
+ version: 1.0.0.rc6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Stegeman
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: overcommit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.55.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.55.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: pry
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -86,28 +100,28 @@ dependencies:
86
100
  requirements:
87
101
  - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: 3.7.0
103
+ version: '3.7'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: 3.7.0
110
+ version: '3.7'
97
111
  - !ruby/object:Gem::Dependency
98
- name: timecop
112
+ name: rubocop
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - "~>"
102
116
  - !ruby/object:Gem::Version
103
- version: 0.9.1
117
+ version: 0.88.0
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
- version: 0.9.1
124
+ version: 0.88.0
111
125
  description: ''
112
126
  email: bstegeman17@gmail.com
113
127
  executables:
@@ -117,7 +131,14 @@ extra_rdoc_files: []
117
131
  files:
118
132
  - bin/piece_of_wax
119
133
  - lib/piece_of_wax.rb
134
+ - lib/piece_of_wax/cli_options.rb
135
+ - lib/piece_of_wax/date_time_decorator/error_message_display.rb
136
+ - lib/piece_of_wax/date_time_decorator/read_display.rb
137
+ - lib/piece_of_wax/date_time_helper.rb
138
+ - lib/piece_of_wax/file_io.rb
120
139
  - lib/piece_of_wax/logfile.rb
140
+ - lib/piece_of_wax/output_line/activity.rb
141
+ - lib/piece_of_wax/output_line/date.rb
121
142
  homepage:
122
143
  licenses:
123
144
  - MIT