piece_of_wax 1.0.0.rc1 → 1.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
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