punched 1.2.0 → 1.3.1

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: 1904b731e6d56b8dc58e49fd74f61d274da5a56342cb97b81a8a9c165f67dcc3
4
- data.tar.gz: d923efb27d5f2f6dea68ae9591a1a2d8ad602ba773a1155eb18642b61bbca434
3
+ metadata.gz: e95894483cb39658bf8b2df9eddece40e114f5afc4c62e8705c5de00345ee3cd
4
+ data.tar.gz: 32431041a7583350ff2f4dd4e1f90c104cae6f100838caf708560a4f9fac5e8a
5
5
  SHA512:
6
- metadata.gz: a98ac0bc3bb08062c85a97098bd5b5f9db1f5adb6f6dac97f02a4838dcde3f99feb91a90da7f45c4b2cf447a36e4cc70c832b1db3c2f0db40ae2d7c8f73a6df6
7
- data.tar.gz: 7f097ad0f236ff27c7eb3dccde336aca01724a04f5613ff4a7eb3dcd9ca3d18438d4ec41cd391a769348ea482fc128971c1ed696ddb2d1d9b3d026c9624a1f02
6
+ metadata.gz: 6d56bd82d7e3e745a0d6c712e3f68af52e65a8df0e0430e68bea264138d36d60a0cef92fcf039ba183307e85e3aab59793525d030c1441fa2709982b742373f7
7
+ data.tar.gz: c99e60f4b12a4848feec37f5454ce1c6b343e5d083821276aa7cb12f050406533b560c02250788736c956e401f05f604afc2ab0c63272a542016623c17f5f0fb
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  Gemfile.lock
2
2
  punchcard_test_data/
3
+ /*.gem
data/.travis.yml CHANGED
@@ -1,11 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1
4
- - 2.2
5
- - 2.3
6
- - 2.4
7
- - 2.5
8
3
  - 2.6
4
+ - 2.7
9
5
  - ruby-head
10
6
  script: bundle exec rspec
11
7
  matrix:
data/README.md CHANGED
@@ -17,49 +17,45 @@
17
17
 
18
18
  ### Usage
19
19
 
20
- #### Start Project
20
+ #### Start and stop a Project
21
21
 
22
22
  ```sh
23
- $ punched start "Punchcard (programming)"
23
+ $ punched toggle punchcard_programming
24
+ 'punchcard_programming' started (00:00:00 total)
24
25
  ```
25
26
 
26
- #### Wildcard
27
-
28
- Save keystrokes by using wildcard. The first last active project, which matches the pattern (case insensitive) will be selected:
27
+ To stop:
29
28
 
30
29
  ```sh
31
- $ punched start "Punch*"
30
+ $ punched toggle punchcard_programming
31
+ 'punchcard_programming' stopped (00:01:25 total)
32
32
  ```
33
33
 
34
- #### Stop Project
34
+ To be more explicit, you can also use `start` and `stop` instead of `toggle`.
35
35
 
36
- ```sh
37
- $ punched stop "Punch*"
38
- ```
39
-
40
- #### Toggle
36
+ #### Wildcard
41
37
 
42
- Toggle between start and stop:
38
+ Save keystrokes by using wildcard. The first last active project, which matches the (case insensitive) pattern will be selected:
43
39
 
44
40
  ```sh
45
- $ punched toggle "Punch*"
41
+ $ punched toggle 'punched*'
46
42
  ```
47
43
 
48
44
  #### Status
49
45
 
50
46
  ```sh
51
- $ punched status "Punch*"
47
+ $ punched status punched_programming
52
48
 
53
- Punchcard (programming)
49
+ punched_programming
54
50
  01:10:09
55
51
  ```
56
52
 
57
53
  #### List details
58
54
 
59
55
  ```sh
60
- $ punched details "Punch*"
56
+ $ punched details punched_programming
61
57
 
62
- Punchcard (programming) (stopped)
58
+ punched_programming (stopped)
63
59
 
64
60
  00:00:08 2017-05-07 08:16:06 - 2017-05-07 08:16:14
65
61
  00:04:35 2017-05-07 08:22:02 - 2017-05-07 08:26:37
@@ -68,26 +64,44 @@ Toggle between start and stop:
68
64
  01:10:04 (total)
69
65
  ```
70
66
 
67
+ #### Filtering
68
+
69
+ You can filter your spend time with `startAt` and `endAt`:
70
+
71
+ ```sh
72
+ $ punched totalsum myproject --startAt=15.05.2020
73
+ $ punched totalsum myproject --startAt=15.05.2020 --endAt=30.05.2020
74
+ $ punched all plain --startAt=15.05.2020 --endAt=30.05.2020
75
+ ```
76
+
71
77
  #### Set Hourly Rate
72
78
 
73
79
  ```sh
74
- $ punched set "Punch*" hourlyRate 250€
80
+ $ punched set punched_programming hourlyRate 250€
81
+ {"hourlyRate":"250€"}
75
82
  ```
76
83
 
77
- #### Total time in seconds
84
+ #### Sum spended time on project(s)
85
+
86
+ `total` returns the total spend time in seconds:
78
87
 
79
88
  ```sh
80
- $ punched total "Punch*"
89
+ $ punched total punched_programming
90
+ 13505
81
91
  ```
82
92
 
83
- #### Rename and delete Project
93
+ `totalsum` calculates human readable spended time on project(s) (wildcard is used by default):
84
94
 
85
95
  ```sh
86
- $ punched rename "Old Title" "New Title"
96
+ $ punched totalsum punched_programming
97
+ 02:05:06
87
98
  ```
88
99
 
100
+ Use `startAt` and/or `endAt` to set a time range:
101
+
89
102
  ```sh
90
- $ punched remove "Punchcard (programming)"
103
+ $ punched totalsum punched_programming --startAt=2020-05-01 --endAt=2020-05-03
104
+ 01:02:36
91
105
  ```
92
106
 
93
107
  #### Help
@@ -107,14 +121,14 @@ List all available actions:
107
121
  |========================================|=========|=====================|================|=============|==========|
108
122
  | project | status | last active on | total duration | hourly rate | earnings |
109
123
  |========================================|=========|=====================|================|=============|==========|
110
- | Website | stopped | 2017-05-07 15:50:00 | 00:04:40 | 95.0 € | 380.00 € |
124
+ | website | stopped | 2017-05-07 15:50:00 | 00:04:40 | 95.0 € | 380.00 € |
111
125
  |----------------------------------------|---------|---------------------|----------------|-------------|----------|
112
- | Punchcard (programming) | stopped | 2017-07-11 12:47:42 | 01:10:04 | | |
126
+ | punchcard_programming | stopped | 2017-07-11 12:47:42 | 01:10:04 | | |
113
127
  |========================================|=========|=====================|================|=============|==========|
114
128
 
115
129
  ```
116
130
 
117
- To use `md` or `csv` as output format:
131
+ To use `plain`, `md` or `csv` as output format:
118
132
 
119
133
  ```sh
120
134
  $ punched all csv
@@ -126,6 +140,8 @@ To use `md` or `csv` as output format:
126
140
 
127
141
  You can use `all` with any other action as well, e.g. `punched all stop` to stop all running projects.
128
142
 
143
+ Here you can also filter your spend time with `startAt` and `endAt`, respectively.
144
+
129
145
  ### Store projects files in a custom folder and sync them between computers
130
146
 
131
147
  By default, PunchCard will store the data in `~/.punchcard/`. Define your custom destination with:
data/bin/punched CHANGED
@@ -2,9 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
5
-
6
5
  begin
7
- require File.expand_path(File.dirname(THIS_FILE) + '/../lib/punchcard')
6
+ require File.expand_path(File.dirname(THIS_FILE) + '/../lib/punchcard.rb')
8
7
  rescue LoadError
9
8
  require 'punchcard'
10
9
  end
@@ -15,19 +14,19 @@ require 'date'
15
14
  require 'json'
16
15
 
17
16
  class UnknownActionError < StandardError; end
18
-
17
+ class InvalidAllArgument < StandardError; end
19
18
  #
20
19
  # CLI Wrapper
21
20
  #
22
21
 
23
22
  def available_actions
24
- PunchCard.new(nil).public_methods(false).reject { |item|
23
+ PunchCard.new(nil).public_methods(false).reject do |item|
25
24
  item.to_s.end_with?('=') || item.to_s == 'project'
26
- }.concat(global_available_actions).sort
25
+ end.concat(global_available_actions).sort
27
26
  end
28
27
 
29
28
  def global_available_actions
30
- [:all, :totalsum]
29
+ %i[all totalsum]
31
30
  end
32
31
 
33
32
  def action_available?(action)
@@ -43,48 +42,58 @@ def usage
43
42
  "Usage: #{File.basename(__FILE__)} #{available_actions.join('|')} 'Name of my project'"
44
43
  end
45
44
 
46
- def all(action)
47
- available_formats = %w[csv plain md]
48
- unless available_formats.include?(action)
49
- raise "Format #{action} is not supported. Possible formats are: #{available_formats.join(',')}"
50
- end
51
-
52
- labels = ['project', 'status', 'last active on', 'total duration', 'hourly rate', 'earnings']
45
+ def list_all_projects(format)
53
46
  data = call_punchcards_by_pattern('*', 'csv').map do |csv_string|
54
47
  csv_data = CSV.parse(csv_string)[0]
55
- last_activity = !csv_data[2].empty? ? DateTime.parse(csv_data[2]).to_time.to_i : 0
48
+ last_activity = !csv_data[2].empty? ? Time.parse(csv_data[2]).to_i : 0
56
49
  csv_data.push(last_activity)
57
50
  csv_data
58
51
  end.sort_by(&:last).reverse.map do |row|
59
52
  row[0...-1]
60
53
  end
61
-
62
54
  return puts('No record(s) so far') if data.empty?
63
55
 
64
- case action
56
+ puts convert_project_data(format, data)
57
+ end
58
+
59
+ def convert_project_data(format, data)
60
+ labels = ['project', 'status', 'last active on', 'total duration', 'hourly rate', 'earnings']
61
+ case format
65
62
  when 'md'
66
- puts MarkdownTables.make_table(labels, data, is_rows: true, align: ['l'])
63
+ MarkdownTables.make_table(labels, data, is_rows: true, align: ['l'])
67
64
  when 'csv'
68
- puts labels.to_csv
69
- puts data.map(&:to_csv).join
65
+ "#{labels.to_csv}\n#{data.map(&:to_csv).join}"
70
66
  when 'plain'
71
67
  table = MarkdownTables.make_table(labels, data, is_rows: true, align: ['l'])
72
- puts MarkdownTables.plain_text(table)
68
+ MarkdownTables.plain_text(table)
69
+ end
70
+ end
71
+
72
+ def all(action)
73
+ available_formats = %w[csv plain md]
74
+ if available_formats.include?(action)
75
+ list_all_projects(action)
76
+ elsif action_available?(action)
77
+ puts call_punchcards_by_pattern('*', action).to_a.join("\n")
78
+ else
79
+ suppurted_arguments = (available_formats + available_actions).reject {|n| n.to_s == 'all' }.uniq
80
+ raise InvalidAllArgument, "'#{action}' is not supported\nSupported formats and actions: #{suppurted_arguments.join(',')}"
73
81
  end
74
82
  end
75
83
 
76
- def call_punchcard(selected_action, project_name)
84
+ def call_punchcard(selected_action:, project_name:, arguments:)
77
85
  if action_available?(selected_action)
78
86
  if !project_name && selected_action != 'list'
79
87
  exit_with_error!("2nd argument has to be the project name, e.g.:\n#{usage}")
80
88
  end
81
89
  punch_card = PunchCard.new project_name
82
90
  begin
83
- arguments = ARGV.drop(2)
84
- if !arguments.empty?
85
- return punch_card.send(selected_action.to_s, *arguments)
91
+ if arguments.nil? || arguments.empty?
92
+ punch_card.public_send(selected_action.to_s)
93
+ elsif arguments.is_a?(Hash)
94
+ punch_card.public_send(selected_action.to_s, **arguments)
86
95
  else
87
- return punch_card.send(selected_action.to_s)
96
+ punch_card.public_send(selected_action.to_s, *arguments)
88
97
  end
89
98
  rescue PunchCardError => e
90
99
  exit_with_error! "Error: #{e.message}"
@@ -94,10 +103,47 @@ def call_punchcard(selected_action, project_name)
94
103
  end
95
104
  end
96
105
 
106
+ def cli_argument_by_name(name)
107
+ value = ARGV.filter { |arg| arg.start_with?("--#{name}=") }&.first
108
+ value = value.split('=')[1] unless value.nil?
109
+ if block_given? && !value.nil? && !value.empty?
110
+ yield value
111
+ else
112
+ value
113
+ end
114
+ end
115
+
97
116
  def call_punchcards_by_pattern(pattern, action)
98
- Dir["#{PunchCard::SETTINGS_DIR}/#{pattern}"].map do |file|
99
- project_name = File.basename(file)
100
- call_punchcard(action, project_name)
117
+ path_patterns = [
118
+ "#{PunchCard::SETTINGS_DIR}/#{pattern}*",
119
+ "#{PunchCard::SETTINGS_DIR}/*#{pattern}*"
120
+ ]
121
+ arguments = {}
122
+ if %w(csv plain md total).include?(action)
123
+ arguments = {
124
+ start_at: cli_argument_by_name('startAt') { |v| Date.parse(v) },
125
+ end_at: cli_argument_by_name('endAt') { |v| Date.parse(v) }
126
+ }
127
+ end
128
+ found = []
129
+ path_patterns.each do |path_pattern|
130
+ found = Dir[path_pattern].map do |file|
131
+ project_name = File.basename(file)
132
+ call_punchcard(
133
+ selected_action: action,
134
+ project_name: project_name,
135
+ arguments: arguments
136
+ )
137
+ end
138
+ break if found.any?
139
+ end
140
+ found
141
+ end
142
+
143
+ def validate_project_name_and_stop_if_invalid(project_name)
144
+ if project_name.strip.strip.start_with?('.')
145
+ STDERR.puts "Error: project name's are not allowed to start with '.'"
146
+ exit 1
101
147
  end
102
148
  end
103
149
 
@@ -114,18 +160,26 @@ if selected_action
114
160
  if selected_action == 'all'
115
161
  all(ARGV[1] || 'plain')
116
162
  elsif selected_action == 'totalsum'
117
- pattern = ARGV[1] || '*'
163
+ pattern_argument = ARGV.slice(1)
164
+ pattern = !pattern_argument || pattern_argument.empty? || pattern_argument.start_with?('--') ? '*' : pattern_argument
118
165
  puts PunchCard.humanize_duration(
119
166
  call_punchcards_by_pattern(pattern, 'total').reduce(&:+)
120
167
  )
121
168
  else
122
- result = call_punchcard(selected_action, project_name)
169
+ validate_project_name_and_stop_if_invalid(project_name)
170
+ result = call_punchcard(
171
+ selected_action: selected_action,
172
+ project_name: project_name,
173
+ arguments: ARGV.drop(2)
174
+ )
123
175
  if result.is_a?(Hash)
124
176
  puts result.to_json
125
177
  else
126
178
  puts result
127
179
  end
128
180
  end
181
+ rescue InvalidAllArgument => e
182
+ exit_with_error! e.message
129
183
  rescue PunchCardError => e
130
184
  exit_with_error! "Error: #{e.message}"
131
185
  rescue UnknownActionError => e
data/lib/punchcard.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # (c) 2017-2019 by Philipp Staender
2
4
 
3
5
  require 'date'
@@ -11,7 +13,7 @@ class PunchCard
11
13
  HOURLY_RATE_PATTERN = /^\s*(\d+)([^\d]+)*\s*/i.freeze
12
14
  TIME_POINT_PATTERN = /^((\d+|.+?\s[\+\-]\d{4}?\s*)(\-)*(\d+|\s.+\d?)*)$/.freeze
13
15
  META_KEY_PATTERN = /^([a-zA-Z0-9]+)\:\s*(.*)$/.freeze
14
- VERSION = '1.2.0'.freeze
16
+ VERSION = '1.3.1'
15
17
 
16
18
  attr_accessor :title
17
19
 
@@ -101,7 +103,7 @@ class PunchCard
101
103
  output.join("\n")
102
104
  end
103
105
 
104
- def csv
106
+ def csv(start_at: nil, end_at: nil)
105
107
  project_exists_or_stop!
106
108
  find_or_make_file
107
109
  durations = []
@@ -110,16 +112,27 @@ class PunchCard
110
112
  points = line_to_time_points(line)
111
113
  next unless points
112
114
 
113
- starttime = points[0]
114
- endtime = points[1] || timestamp
115
+ start_time = points[0]
116
+ end_time = points[1] || timestamp
117
+
118
+ next if time_range_is_excluded_by_filter?(
119
+ start_at: start_at,
120
+ end_at: end_at,
121
+ start_time: start_time,
122
+ end_time: end_time
123
+ )
124
+
115
125
  last_activity = points[1] || points[0]
116
- durations.push duration(starttime, endtime)
126
+ durations.push end_time - start_time
117
127
  end
128
+ total_duration = self.class.humanize_duration(
129
+ durations.reduce(&:+) || 0
130
+ )
118
131
  '"' + [
119
132
  title_and_project,
120
133
  running_status,
121
134
  last_activity ? self.class.format_time(Time.at(last_activity).to_datetime) : '',
122
- humanized_total,
135
+ total_duration,
123
136
  hourly_rate ? hourly_rate[:hourlyRate].to_s + " #{hourly_rate[:currency]}" : '',
124
137
  hourly_rate ? (hourly_rate[:hourlyRate] * total / 3600.0).round(2).to_s + " #{hourly_rate[:currency]}" : ''
125
138
  ].join('","') + '"'
@@ -155,22 +168,32 @@ class PunchCard
155
168
  end
156
169
 
157
170
  def set(key, value)
158
- raise PunchCardError, "Key '#{key}' can only be alphanumeric" unless key =~ /^[a-zA-Z0-9]+$/
171
+ unless key =~ /^[a-zA-Z0-9]+$/
172
+ raise PunchCardError, "Key '#{key}' can only be alphanumeric"
173
+ end
159
174
 
160
175
  @meta_data[key.to_sym] = value
161
176
  write_to_project_file!
162
177
  @meta_data
163
178
  end
164
179
 
165
- def total
180
+ def total(start_at: nil, end_at: nil)
166
181
  total = 0
167
182
  project_data.map do |line|
168
183
  points = line_to_time_points(line)
169
184
  next unless points
170
185
 
171
- starttime = points[0]
172
- endtime = points[1] || timestamp
173
- total += endtime - starttime
186
+ start_time = points[0]
187
+ end_time = points[1] || timestamp
188
+
189
+ next if time_range_is_excluded_by_filter?(
190
+ start_at: start_at,
191
+ end_at: end_at,
192
+ start_time: start_time,
193
+ end_time: end_time
194
+ )
195
+
196
+ total += end_time - start_time
174
197
  end
175
198
  total
176
199
  end
@@ -180,6 +203,8 @@ class PunchCard
180
203
  end
181
204
 
182
205
  def self.humanize_duration(duration)
206
+ return nil unless duration
207
+
183
208
  hours = duration / (60 * 60)
184
209
  minutes = (duration / 60) % 60
185
210
  seconds = duration % 60
@@ -197,7 +222,7 @@ class PunchCard
197
222
  private
198
223
 
199
224
  def hourly_rate
200
- hourly_rate_found = @meta_data[:hourlyRate] && @meta_data[:hourlyRate].match(HOURLY_RATE_PATTERN)
225
+ hourly_rate_found = @meta_data[:hourlyRate]&.match(HOURLY_RATE_PATTERN)
201
226
  return unless hourly_rate_found
202
227
 
203
228
  {
@@ -222,9 +247,9 @@ class PunchCard
222
247
  self.class.humanize_duration total
223
248
  end
224
249
 
225
- def duration(starttime, endtime)
226
- if starttime
227
- self.class.humanize_duration endtime - starttime
250
+ def duration(start_time, end_time)
251
+ if start_time
252
+ self.class.humanize_duration end_time - start_time
228
253
  else
229
254
  self.class.humanize_duration 0
230
255
  end
@@ -254,7 +279,7 @@ class PunchCard
254
279
  matches = line.match(TIME_POINT_PATTERN)
255
280
 
256
281
  time_points = matches ? [string_to_timestamp(matches[2]), string_to_timestamp(matches[4])] : nil
257
- if time_points && time_points.reject(&:nil?).empty?
282
+ if time_points&.reject(&:nil?)&.empty?
258
283
  nil
259
284
  else
260
285
  time_points
@@ -351,4 +376,8 @@ class PunchCard
351
376
  def sanitize_filename(name)
352
377
  name.downcase.gsub(%r{(\\|/)}, '').gsub(/[^0-9a-z.\-]/, '_')
353
378
  end
379
+
380
+ def time_range_is_excluded_by_filter?(start_time:, end_time:, start_at: nil, end_at: nil)
381
+ start_at && start_at.to_time.to_i >= start_time || end_at && end_at.to_time.to_i <= end_time
382
+ end
354
383
  end
data/punched.gemspec CHANGED
@@ -11,9 +11,8 @@ Gem::Specification.new do |s|
11
11
  s.description = 'Minimal time tracking tool for cli'
12
12
  s.license = 'GPL-3.0'
13
13
  s.executables = ['punched']
14
- s.rubyforge_project = 'punched'
15
14
  s.require_paths = ['lib']
16
- s.required_ruby_version = '>= 2.1'
15
+ s.required_ruby_version = '>= 2.6'
17
16
  s.files = `git ls-files`.split("\n")
18
17
 
19
18
  s.add_dependency 'markdown-tables', '~> 1.0.2'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  $LOAD_PATH.push File.expand_path('lib', __dir__)
2
3
 
3
4
  require 'punchcard'
@@ -13,6 +14,10 @@ def setup_example_settings_dir
13
14
  PunchCard.const_set(:SETTINGS_DIR, example_settings_dir)
14
15
  end
15
16
 
17
+ def punched_bin
18
+ "PUNCHCARD_DIR=#{example_settings_dir} #{Dir.pwd}/bin/punched"
19
+ end
20
+
16
21
  describe PunchCard do
17
22
  before do
18
23
  setup_example_settings_dir
@@ -86,6 +91,13 @@ describe PunchCard do
86
91
  expect(project.project).to eq('playing_mot_rhead')
87
92
  end
88
93
 
94
+ xit 'should read and write utf8 names' do
95
+ PunchCard.new 'Playing Motörhead'
96
+ expect(my_project_file('playing_mot_rhead').strip).to eq('Playing Motörhead')
97
+ project = PunchCard.new 'Playing*'
98
+ expect(project.project).to eq('Playing Motörhead')
99
+ end
100
+
89
101
  it 'should set hourlyRate' do
90
102
  project = start_and_stop
91
103
  project.set 'hourlyRate', '1000 €'
@@ -150,4 +162,10 @@ describe PunchCard do
150
162
  project.remove
151
163
  expect(File.exist?("#{example_settings_dir}/my_project")).to be_falsey
152
164
  end
165
+
166
+ it 'should call punched all' do
167
+ two_seconds_tracking
168
+ result = `#{punched_bin} all`
169
+ expect(result).to match('My Project')
170
+ end
153
171
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: punched
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philipp Staender
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-08 00:00:00.000000000 Z
11
+ date: 2020-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: markdown-tables
@@ -45,7 +45,7 @@ homepage: https://github.com/pstaender/punchcard
45
45
  licenses:
46
46
  - GPL-3.0
47
47
  metadata: {}
48
- post_install_message:
48
+ post_install_message:
49
49
  rdoc_options: []
50
50
  require_paths:
51
51
  - lib
@@ -53,15 +53,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
- version: '2.1'
56
+ version: '2.6'
57
57
  required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  requirements: []
63
- rubygems_version: 3.0.3
64
- signing_key:
63
+ rubygems_version: 3.0.6
64
+ signing_key:
65
65
  specification_version: 4
66
66
  summary: Punchcard Timetracker
67
67
  test_files: []