gpuzzletime 0.3.2 → 0.4.0

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: ad32482efc9bdb226539c6ff78448407dc53abd9290d245d9d90303c97b8a8d4
4
- data.tar.gz: f10bf0669eb02e48aa06ea0f668af33b79a641f88c1dc0fbbc578dfa9694f357
3
+ metadata.gz: da04f6e2158de538ed579e152023e1577f4743eaf869148398cad63cc4602e7e
4
+ data.tar.gz: 1cbf496d462a395a0466e7fe0d94ef54ef3bd59c64caccfa7c3a77e5ca013fe7
5
5
  SHA512:
6
- metadata.gz: 94eb248ad45b98d633af96960952edbe7067d0fed37623769b0ca1d7fb1830f39b4173b9ac84608c26bb7c5a9256ca323a731a1a2c202e1c5be4b1c366fa6c71
7
- data.tar.gz: cf5212b3a8846f48e4a09f02deef0337ec5ce01861797982c53670e67610549af3427ec89705704b06ac44add75ceeb19a0982f9522948cb11f7c84fb05bb039
6
+ metadata.gz: e35352a95c6bc0bda0ce852f7b95c9681ca508d094239b754d5081fdb625c0d8e0eafd50983206b70ef2fbd7656164f3965d4d730612eec786c6dd1fe580ae4d
7
+ data.tar.gz: 8ca1ea92a1036ce3f9a791d2d565728dcc7ecd2afe510cb04f60579e57d0ce3eaf78d7e5c7fb9f3208ec58cbac01c8117e5b3361befc8b4c2b8599869d14d860
data/.rubocop.yml CHANGED
@@ -1,6 +1,12 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  Metrics/LineLength:
2
4
  Max: 120
3
5
 
6
+ Metrics/BlockLength:
7
+ Exclude:
8
+ - spec/**/*
9
+
4
10
  Style/TrailingCommaInArrayLiteral:
5
11
  EnforcedStyleForMultiline: consistent_comma
6
12
 
@@ -11,7 +17,3 @@ Layout/AlignHash:
11
17
  EnforcedHashRocketStyle: table
12
18
  EnforcedColonStyle: table
13
19
  EnforcedLastArgumentHashStyle: always_inspect # default
14
-
15
- Metrics/BlockLength:
16
- Exclude:
17
- - spec/**/*
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,25 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2019-06-11 08:46:25 +0200 using RuboCop version 0.65.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ Metrics/AbcSize:
11
+ Max: 28
12
+
13
+ # Offense count: 1
14
+ # Configuration parameters: CountComments.
15
+ Metrics/ClassLength:
16
+ Max: 153
17
+
18
+ # Offense count: 1
19
+ Metrics/CyclomaticComplexity:
20
+ Max: 8
21
+
22
+ # Offense count: 3
23
+ # Configuration parameters: CountComments, ExcludedMethods.
24
+ Metrics/MethodLength:
25
+ Max: 27
data/README.md CHANGED
@@ -25,7 +25,7 @@ small tooling to transfer timelog-entries from gtimelog's timelog.txt to the Puz
25
25
  - [ ] open day in browser for review
26
26
  - [ ] avoid duplicate entries
27
27
  - [ ] start/end time as indicator?
28
- - [ ] offer rounding times to the next 5, 10 or 15 minutes
28
+ - [x] offer rounding times to the next 5, 10 or 15 minutes
29
29
  - [ ] allow to add entries from the command-line
30
30
  - [ ] handle time-account and billable better
31
31
  - [ ] import time-accounts from ptime (https://time.puzzle.ch/work_items/search.json?q=search%20terms)
@@ -83,19 +83,27 @@ Otherwise, a script to determine the time-account is loaded.
83
83
  gpuzzletime can prefill the account-number and billable-state of an entry.
84
84
 
85
85
  The tags are used to determine a script that helps infer the time-account.
86
- These scripts should be located in ~/.config/gpuzzletime/parsers/ and be named
86
+ These scripts should be located in `~/.config/gpuzzletime/parsers/` and be named
87
87
  like the first tag used. The script gets the ticket, the description and all
88
88
  remaining tags passed as arguments. The output of the script should only be the
89
89
  ID of the time-account.
90
90
 
91
91
  In order to infer the billable-state of an entry, a script
92
- ~/.config/gpuzzletime/billable is called. It only gets the previously infered
92
+ `~/.config/gpuzzletime/billable` is called. It only gets the previously infered
93
93
  account-id as argument and is expected to output "true" or "false".
94
94
 
95
95
  Since these script are called a lot, it is better to write them in a compiled
96
96
  language. If you only like ruby, take a look at crystal. For such simple
97
97
  scripts, the code is almost identical and "just" needs to be compiled.
98
98
 
99
+ ## Configuration
100
+
101
+ A config-file is read from `$HOME/.config/gpuzzletime/config`. It is expected
102
+ to be a YAML-file. Currently, it supports the following keys:
103
+
104
+ - rounding: [integer or false, default 15]
105
+ - base_url: [url to your puzzletime-installation, default https://time.puzzle.ch]
106
+
99
107
  ## Development
100
108
 
101
109
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
data/Rakefile CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
5
6
 
6
7
  RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
7
9
 
8
- task default: :spec
10
+ task default: %i[rubocop spec]
data/lib/gpuzzletime.rb CHANGED
@@ -5,5 +5,6 @@ $LOAD_PATH.unshift File.dirname(__FILE__)
5
5
  # Autoloading and such
6
6
  module Gpuzzletime
7
7
  autoload :App, 'gpuzzletime/app'
8
+ autoload :Timelog, 'gpuzzletime/timelog'
8
9
  autoload :VERSION, 'gpuzzletime/version'
9
10
  end
@@ -7,9 +7,14 @@ require 'pathname'
7
7
  module Gpuzzletime
8
8
  # Wrapper for everything
9
9
  class App
10
- def initialize(args)
11
- @base_url = 'https://time.puzzle.ch'
10
+ CONFIGURATION_DEFAULTS = {
11
+ base_url: 'https://time.puzzle.ch',
12
+ rounding: 15,
13
+ dir: Pathname.new('~/.config/gpuzzletime').expand_path,
14
+ }.freeze
12
15
 
16
+ def initialize(args)
17
+ @config = load_config(CONFIGURATION_DEFAULTS[:dir].join('config'))
13
18
  @command = (args[0] || :show).to_sym
14
19
 
15
20
  case @command
@@ -48,22 +53,32 @@ module Gpuzzletime
48
53
 
49
54
  private
50
55
 
56
+ def load_config(config_fn)
57
+ user_config = config_fn.exist? ? YAML.load_file(config_fn) : {}
58
+
59
+ CONFIGURATION_DEFAULTS.merge(user_config)
60
+ end
61
+
51
62
  def entries
52
63
  @entries ||= {}
53
64
  end
54
65
 
66
+ def timelog
67
+ Timelog.load
68
+ end
69
+
55
70
  def fill_entries(purpose)
56
- parse(read).each do |date, lines|
71
+ timelog.each do |date, lines|
57
72
  # this is mixing preparation, assembly and output, but gets the job done
58
73
  next unless date # guard against the machine
59
74
  next unless @date == :all || @date == date # limit to one day if passed
60
75
 
61
76
  entries[date] = []
62
77
 
63
- start = nil # at the start of the day, we have no previous end
78
+ start = nil # at the start of the day, we have no previous end
64
79
 
65
80
  lines.each do |entry|
66
- finish = entry[:time] # we use that twice
81
+ finish = round_time(entry[:time], @config[:rounding]) # we use that twice
67
82
  hidden = entry[:description].match(/\*\*$/) # hide lunch and breaks
68
83
 
69
84
  if start && !hidden
@@ -88,12 +103,25 @@ module Gpuzzletime
88
103
  end
89
104
  end
90
105
 
106
+ def round_time(time, interval)
107
+ return time unless interval
108
+
109
+ hour, minute = time.split(':')
110
+ minute = (minute.to_i / interval.to_f).round * interval.to_i
111
+
112
+ if minute == 60
113
+ [hour.succ, 0]
114
+ else
115
+ [hour, minute]
116
+ end.map { |part| part.to_s.rjust(2, '0') }.join(':')
117
+ end
118
+
91
119
  def open_browser(start, entry)
92
- xdg_open "'#{@base_url}/ordertimes/new?#{url_options(start, entry)}'", silent: true
120
+ xdg_open "'#{@config[:base_url]}/ordertimes/new?#{url_options(start, entry)}'", silent: true
93
121
  end
94
122
 
95
123
  def xdg_open(args, silent: false)
96
- opener = 'xdg-open'
124
+ opener = 'xdg-open' # could be configurable, but is already a proxy
97
125
  silencer = '> /dev/null 2> /dev/null'
98
126
 
99
127
  if system("which #{opener} #{silencer}")
@@ -104,6 +132,9 @@ module Gpuzzletime
104
132
 
105
133
  This binary is needed to launch a webbrowser and open the page
106
134
  to enter the worktime-entry into puzzletime.
135
+
136
+ If this needs to be configurable, please open an issue at
137
+ https://github.com/kronn/gpuzzletime/issues/new
107
138
  ERRORMESSAGE
108
139
  end
109
140
  end
@@ -111,7 +142,7 @@ module Gpuzzletime
111
142
  def launch_editor
112
143
  editor = `which $EDITOR`.chomp
113
144
 
114
- file = @file.nil? ? timelog_txt : parser_file(@file)
145
+ file = @file.nil? ? Timelog.timelog_txt : parser_file(@file)
115
146
 
116
147
  exec "#{editor} #{file}"
117
148
  end
@@ -123,7 +154,7 @@ module Gpuzzletime
123
154
  'ordertime[ticket]': entry[:ticket],
124
155
  'ordertime[description]': entry[:description],
125
156
  'ordertime[from_start_time]': start,
126
- 'ordertime[to_end_time]': entry[:time],
157
+ 'ordertime[to_end_time]': round_time(entry[:time], @config[:rounding]),
127
158
  'ordertime[account_id]': account,
128
159
  'ordertime[billable]': infer_billable(account),
129
160
  }
@@ -135,40 +166,14 @@ module Gpuzzletime
135
166
  case date
136
167
  when 'yesterday' then Date.today.prev_day.to_s
137
168
  when 'today' then Date.today.to_s
138
- when 'last' then parse(read).to_h.keys.compact.sort[-2] || Date.today.prev_day.to_s
169
+ when 'last' then timelog.to_h.keys.compact.sort[-2] || Date.today.prev_day.to_s
139
170
  when /\d{4}(-\d{2}){2}/ then date
140
171
  end
141
172
  end
142
173
 
143
- def timelog_txt
144
- Pathname.new('~/.local/share/gtimelog/timelog.txt').expand_path
145
- end
146
-
147
- def read
148
- timelog_txt.read
149
- end
150
-
151
- def parse(data)
152
- data.split("\n")
153
- .map { |line| tokenize(line) }
154
- .group_by { |match| match && match[:date] }
155
- .to_a
156
- end
157
-
158
- def tokenize(line)
159
- re_date = /(?<date>\d{4}-\d{2}-\d{2})/
160
- re_time = /(?<time>\d{2}:\d{2})/
161
- re_tick = /(?:(?<ticket>.*?): )/
162
- re_desc = /(?<description>.*?)/
163
- re_tags = /(?: -- (?<tags>.*)?)/
164
-
165
- regexp = /^#{re_date} #{re_time}: #{re_tick}?#{re_desc}#{re_tags}?$/
166
- line.match(regexp)
167
- end
168
-
169
174
  def parser_file(parser_name)
170
- Pathname.new("~/.config/gpuzzletime/parsers/#{parser_name}") # FIXME: security-hole, prevent relative paths!
171
- .expand_path
175
+ @config[:dir].join("parsers/#{parser_name}") # FIXME: security-hole, prevent relative paths!
176
+ .expand_path
172
177
  end
173
178
 
174
179
  def infer_account(entry)
@@ -186,7 +191,7 @@ module Gpuzzletime
186
191
  end
187
192
 
188
193
  def infer_billable(account)
189
- script = Pathname.new('~/.config/gpuzzletime/billable').expand_path
194
+ script = @config[:dir].join('billable')
190
195
 
191
196
  return 1 unless script.exist?
192
197
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module Gpuzzletime
6
+ # Load and tokenize the data from gtimelog
7
+ class Timelog
8
+ class << self
9
+ def load
10
+ new.load
11
+ end
12
+
13
+ def timelog_txt
14
+ Pathname.new('~/.local/share/gtimelog/timelog.txt').expand_path
15
+ end
16
+ end
17
+
18
+ def load
19
+ parse(read)
20
+ end
21
+
22
+ def timelog_txt
23
+ self.class.timelog_txt
24
+ end
25
+
26
+ def read
27
+ timelog_txt.read
28
+ end
29
+
30
+ def parse(data)
31
+ data.split("\n")
32
+ .map { |line| tokenize(line) }
33
+ .group_by { |match| match && match[:date] }
34
+ .to_a
35
+ end
36
+
37
+ def tokenize(line)
38
+ re_date = /(?<date>\d{4}-\d{2}-\d{2})/
39
+ re_time = /(?<time>\d{2}:\d{2})/
40
+ re_tick = /(?:(?<ticket>.*?): )/
41
+ re_desc = /(?<description>.*?)/
42
+ re_tags = /(?: -- (?<tags>.*)?)/
43
+
44
+ regexp = /^#{re_date} #{re_time}: #{re_tick}?#{re_desc}#{re_tags}?$/
45
+ line.match(regexp)
46
+ end
47
+ end
48
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version
4
4
  module Gpuzzletime
5
- VERSION = '0.3.2'
5
+ VERSION = '0.4.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gpuzzletime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Viehweger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-26 00:00:00.000000000 Z
11
+ date: 2019-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -120,6 +120,7 @@ files:
120
120
  - ".overcommit.yml"
121
121
  - ".rspec"
122
122
  - ".rubocop.yml"
123
+ - ".rubocop_todo.yml"
123
124
  - ".ruby-gemset"
124
125
  - ".ruby-version"
125
126
  - ".travis.yml"
@@ -133,6 +134,7 @@ files:
133
134
  - gpuzzletime.gemspec
134
135
  - lib/gpuzzletime.rb
135
136
  - lib/gpuzzletime/app.rb
137
+ - lib/gpuzzletime/timelog.rb
136
138
  - lib/gpuzzletime/version.rb
137
139
  homepage: https://github.com/kronn/gpuzzletime
138
140
  licenses: