doing 1.0.21 → 1.0.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/bin/doing +14 -6
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +280 -52
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73e09fb9dbc14de53c2464dba86178c402ecdba46d89873d83aea2abe97e1454
|
4
|
+
data.tar.gz: d8c1d0a5762a7fbef26522f1664414538647a6fd5b4b5f466844b4a1503cad98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 968381dd7b6c260a550f6dad755a54f2388baaaaa84ace9a7ea1feafad92b889db04b3084f8236296c4b2bca45613abf2f2d11147b9c0afecf9dd21b869fdaac
|
7
|
+
data.tar.gz: f585f589879ea6f4c772a71be98c7d31622eee2c3a6a32b42d268890590f0239fb6755eb75d44bf14cf9d559460190edb0980655c535e75f350970fde1ad11ec
|
data/README.md
CHANGED
@@ -95,7 +95,9 @@ A basic configuration looks like this:
|
|
95
95
|
|
96
96
|
### Per-folder configuration
|
97
97
|
|
98
|
-
Any options found in a `.doingrc` anywhere in the hierarchy between your current folder and your home folder will be appended to the base configuration, overriding or extending existing options. This allows you to put a `.doingrc` file into the base of a project and add specific configurations (such as default tags) when working in that project on the command line.
|
98
|
+
Any options found in a `.doingrc` anywhere in the hierarchy between your current folder and your home folder will be appended to the base configuration, overriding or extending existing options. This allows you to put a `.doingrc` file into the base of a project and add specific configurations (such as default tags) when working in that project on the command line. These can be cascaded, with the closest `.doingrc` to your current directory taking precedence, though I'm not sure why you'd want to deal with that.
|
99
|
+
|
100
|
+
This can also be used to define custom HTML output on a per-project basis using the html_template option for custom templates.
|
99
101
|
|
100
102
|
Any part of the configuration can be copied into these local files and modified. You only need to include the parts you want to change or add.
|
101
103
|
|
@@ -550,6 +552,12 @@ Feel free to [poke around](http://github.com/ttscoff/doing/), I'll try to add mo
|
|
550
552
|
|
551
553
|
## Changelog
|
552
554
|
|
555
|
+
#### 1.0.22
|
556
|
+
|
557
|
+
- Fix handling of "local" config files, allowing per-project configurations
|
558
|
+
- Allow cascading of local config files
|
559
|
+
- Allow `doing today` and `yesterday` to specify a section
|
560
|
+
|
553
561
|
#### 1.0.21
|
554
562
|
|
555
563
|
- Add legitimate regex search capabilities
|
data/bin/doing
CHANGED
@@ -555,7 +555,7 @@ end
|
|
555
555
|
|
556
556
|
desc 'List all entries'
|
557
557
|
long_desc 'The argument can be a section name, @tag(s) or both. "pick" or "choose" as an argument will offer a section menu.'
|
558
|
-
arg_name 'section
|
558
|
+
arg_name '[section|@tags]'
|
559
559
|
command :show do |c|
|
560
560
|
c.desc 'Tag boolean (AND,OR,NONE)'
|
561
561
|
c.default_value "OR"
|
@@ -723,6 +723,11 @@ end
|
|
723
723
|
|
724
724
|
desc 'List entries from today'
|
725
725
|
command :today do |c|
|
726
|
+
c.desc 'Specify a section'
|
727
|
+
c.arg_name 'section_name'
|
728
|
+
c.default_value "All"
|
729
|
+
c.flag [:s,:section], :default_value => 'All'
|
730
|
+
|
726
731
|
c.desc 'Show time intervals on @done tasks'
|
727
732
|
c.default_value true
|
728
733
|
c.switch [:t,:times], :default_value => true
|
@@ -738,7 +743,7 @@ command :today do |c|
|
|
738
743
|
|
739
744
|
options[:t] = true if options[:totals]
|
740
745
|
|
741
|
-
puts wwid.today(options[:t],options[:output],{:totals => options[:totals]}).chomp
|
746
|
+
puts wwid.today(options[:t],options[:output],{:totals => options[:totals], :section => options[:s]}).chomp
|
742
747
|
|
743
748
|
end
|
744
749
|
end
|
@@ -790,9 +795,12 @@ command :on do |c|
|
|
790
795
|
end
|
791
796
|
|
792
797
|
desc 'List entries from yesterday'
|
793
|
-
default_value wwid.current_section
|
794
|
-
arg_name 'section'
|
795
798
|
command :yesterday do |c|
|
799
|
+
c.desc 'Specify a section'
|
800
|
+
c.arg_name 'section_name'
|
801
|
+
c.default_value "All"
|
802
|
+
c.flag [:s,:section], :default_value => 'All'
|
803
|
+
|
796
804
|
c.desc 'Output to export format (csv|html|json)'
|
797
805
|
c.flag [:o,:output]
|
798
806
|
|
@@ -806,7 +814,7 @@ command :yesterday do |c|
|
|
806
814
|
|
807
815
|
c.action do |global_options, options,args|
|
808
816
|
section = args.length > 0 ? args.join(" ") : wwid.current_section
|
809
|
-
puts wwid.yesterday(
|
817
|
+
puts wwid.yesterday(options[:s],options[:t],options[:o],{:totals => options[:totals]}).chomp
|
810
818
|
|
811
819
|
end
|
812
820
|
end
|
@@ -814,7 +822,7 @@ end
|
|
814
822
|
desc 'Show the last entry'
|
815
823
|
command :last do |c|
|
816
824
|
c.desc 'Specify a section'
|
817
|
-
c.default_value
|
825
|
+
c.default_value "All"
|
818
826
|
c.flag [:s,:section]
|
819
827
|
|
820
828
|
c.action do |global_options,options,args|
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -3,6 +3,9 @@
|
|
3
3
|
require 'deep_merge'
|
4
4
|
require 'pp'
|
5
5
|
|
6
|
+
##
|
7
|
+
## @brief String helpers
|
8
|
+
##
|
6
9
|
class String
|
7
10
|
def cap_first
|
8
11
|
self.sub(/^\w/) do |m|
|
@@ -10,6 +13,11 @@ class String
|
|
10
13
|
end
|
11
14
|
end
|
12
15
|
|
16
|
+
##
|
17
|
+
## @brief Turn raw urls into HTML links
|
18
|
+
##
|
19
|
+
## @param opt (Hash) Additional Options
|
20
|
+
##
|
13
21
|
def link_urls(opt={})
|
14
22
|
opt[:format] ||= :html
|
15
23
|
if opt[:format] == :html
|
@@ -31,20 +39,32 @@ class String
|
|
31
39
|
end
|
32
40
|
end
|
33
41
|
|
42
|
+
##
|
43
|
+
## @brief Main "What Was I Doing" methods
|
44
|
+
##
|
34
45
|
class WWID
|
35
46
|
attr_accessor :content, :sections, :current_section, :doing_file, :config, :user_home, :default_config_file, :results
|
36
47
|
|
48
|
+
##
|
49
|
+
## @brief Initializes the object.
|
50
|
+
##
|
37
51
|
def initialize
|
38
52
|
@content = {}
|
39
53
|
@doingrc_needs_update = false
|
40
54
|
@default_config_file = '.doingrc'
|
41
55
|
end
|
42
56
|
|
57
|
+
##
|
58
|
+
## @brief Read user configuration and merge with defaults
|
59
|
+
##
|
60
|
+
## @param opt (Hash) Additional Options
|
61
|
+
##
|
43
62
|
def configure(opt={})
|
44
63
|
@timers = {}
|
45
64
|
@config_file == File.join(@user_home, @default_config_file)
|
46
65
|
|
47
|
-
|
66
|
+
read_config
|
67
|
+
|
48
68
|
user_config = @config.dup
|
49
69
|
@results = []
|
50
70
|
|
@@ -110,10 +130,9 @@ class WWID
|
|
110
130
|
|
111
131
|
@config[:include_notes] ||= true
|
112
132
|
|
113
|
-
File.open(@config_file, 'w') { |yf| YAML::dump(@config, yf) }
|
114
|
-
|
133
|
+
File.open(@config_file, 'w') { |yf| YAML::dump(@config, yf) } unless @config == user_config
|
115
134
|
|
116
|
-
@config = @
|
135
|
+
@config = @local_config.deep_merge(@config)
|
117
136
|
|
118
137
|
@current_section = @config['current_section']
|
119
138
|
@default_template = @config['templates']['default']['template']
|
@@ -121,9 +140,16 @@ class WWID
|
|
121
140
|
|
122
141
|
end
|
123
142
|
|
124
|
-
|
143
|
+
##
|
144
|
+
## @brief Initializes the doing file.
|
145
|
+
##
|
146
|
+
## @param path (String) Override path to a doing file, optional
|
147
|
+
##
|
148
|
+
def init_doing_file(path=nil)
|
125
149
|
@doing_file = File.expand_path(@config['doing_file'])
|
126
150
|
|
151
|
+
input = path
|
152
|
+
|
127
153
|
if input.nil?
|
128
154
|
create(@doing_file) unless File.exists?(@doing_file)
|
129
155
|
input = IO.read(@doing_file)
|
@@ -178,14 +204,27 @@ class WWID
|
|
178
204
|
}
|
179
205
|
end
|
180
206
|
|
207
|
+
##
|
208
|
+
## @brief Return the contents of the HAML template for HTML output
|
209
|
+
##
|
210
|
+
## @return (String) HAML template
|
211
|
+
##
|
181
212
|
def haml_template
|
182
213
|
IO.read(File.join(File.dirname(__FILE__), '../templates/doing.haml'))
|
183
214
|
end
|
184
215
|
|
216
|
+
##
|
217
|
+
## @brief Return the contents of the CSS template for HTML output
|
218
|
+
##
|
219
|
+
## @return (String) CSS template
|
220
|
+
##
|
185
221
|
def css_template
|
186
222
|
IO.read(File.join(File.dirname(__FILE__), '../templates/doing.css'))
|
187
223
|
end
|
188
224
|
|
225
|
+
##
|
226
|
+
## @brief Create a new doing file
|
227
|
+
##
|
189
228
|
def create(filename=nil)
|
190
229
|
if filename.nil?
|
191
230
|
filename = @doing_file
|
@@ -197,37 +236,51 @@ class WWID
|
|
197
236
|
end
|
198
237
|
end
|
199
238
|
|
239
|
+
##
|
240
|
+
## @brief Finds a project-specific configuration file
|
241
|
+
##
|
242
|
+
## @return (String) A file path
|
243
|
+
##
|
200
244
|
def find_local_config
|
201
245
|
|
202
246
|
config = {}
|
203
247
|
dir = Dir.pwd
|
204
248
|
|
205
|
-
|
249
|
+
local_config_files = []
|
206
250
|
|
207
251
|
while (dir != '/' && (dir =~ /[A-Z]:\//) == nil)
|
208
252
|
if File.exists? File.join(dir, @default_config_file)
|
209
|
-
|
253
|
+
local_config_files.push(File.join(dir, @default_config_file))
|
210
254
|
end
|
211
255
|
|
212
256
|
dir = File.dirname(dir)
|
213
257
|
end
|
214
258
|
|
215
|
-
|
259
|
+
local_config_files
|
216
260
|
end
|
217
261
|
|
262
|
+
##
|
263
|
+
## @brief Reads a configuration.
|
264
|
+
##
|
218
265
|
def read_config
|
219
266
|
if Dir.respond_to?('home')
|
220
267
|
@config_file = File.join(Dir.home, @default_config_file)
|
221
268
|
else
|
222
269
|
@config_file = File.join(File.expand_path("~"), @default_config_file)
|
223
270
|
end
|
224
|
-
@doingrc_needs_update = true if File.exists? @config_file
|
225
|
-
|
271
|
+
# @doingrc_needs_update = true if File.exists? @config_file
|
272
|
+
additional_configs = find_local_config
|
226
273
|
|
227
274
|
begin
|
275
|
+
@local_config = {}
|
276
|
+
|
228
277
|
@config = YAML.load_file(@config_file) || {} if File.exists?(@config_file)
|
229
|
-
|
230
|
-
|
278
|
+
additional_configs.each { |cfg|
|
279
|
+
new_config = YAML.load_file(cfg) || {} if cfg
|
280
|
+
@local_config = @local_config.deep_merge(new_config)
|
281
|
+
}
|
282
|
+
|
283
|
+
# @config.deep_merge(@local_config)
|
231
284
|
rescue
|
232
285
|
@config = {}
|
233
286
|
@local_config = {}
|
@@ -235,6 +288,11 @@ class WWID
|
|
235
288
|
end
|
236
289
|
end
|
237
290
|
|
291
|
+
##
|
292
|
+
## @brief Create a process for an editor and wait for the file handle to return
|
293
|
+
##
|
294
|
+
## @param input (String) Text input for editor
|
295
|
+
##
|
238
296
|
def fork_editor(input="")
|
239
297
|
tmpfile = Tempfile.new(['doing','.md'])
|
240
298
|
|
@@ -268,11 +326,13 @@ class WWID
|
|
268
326
|
input
|
269
327
|
end
|
270
328
|
|
271
|
-
#
|
272
|
-
#
|
273
|
-
#
|
274
|
-
#
|
275
|
-
#
|
329
|
+
#
|
330
|
+
# @brief Takes a multi-line string and formats it as an entry
|
331
|
+
#
|
332
|
+
# @return (Array) [(String)title, (Array)note]
|
333
|
+
#
|
334
|
+
# @param input (String) The string to parse
|
335
|
+
#
|
276
336
|
def format_input(input)
|
277
337
|
raise "No content in entry" if input.nil? || input.strip.length == 0
|
278
338
|
input_lines = input.split(/[\n\r]+/)
|
@@ -287,11 +347,17 @@ class WWID
|
|
287
347
|
[title, note]
|
288
348
|
end
|
289
349
|
|
290
|
-
#
|
291
|
-
#
|
292
|
-
#
|
293
|
-
#
|
294
|
-
#
|
350
|
+
#
|
351
|
+
# @brief Converts input string into a Time object when input takes on the
|
352
|
+
# following formats:
|
353
|
+
# - interval format e.g. '1d2h30m', '45m' etc.
|
354
|
+
# - a semantic phrase e.g. 'yesterday 5:30pm'
|
355
|
+
# - a strftime e.g. '2016-03-15 15:32:04 PDT'
|
356
|
+
#
|
357
|
+
# @param input (String) String to chronify
|
358
|
+
#
|
359
|
+
# @return (DateTime) result
|
360
|
+
#
|
295
361
|
def chronify(input)
|
296
362
|
now = Time.now
|
297
363
|
raise "Invalid time expression #{input.inspect}" if input.to_s.strip == ""
|
@@ -312,10 +378,15 @@ class WWID
|
|
312
378
|
end
|
313
379
|
end
|
314
380
|
|
315
|
-
#
|
316
|
-
#
|
317
|
-
#
|
318
|
-
#
|
381
|
+
#
|
382
|
+
# @brief Converts simple strings into seconds that can be added to a Time
|
383
|
+
# object
|
384
|
+
#
|
385
|
+
# @param qty (String) HH:MM or XX[dhm][[XXhm][XXm]] (1d2h30m, 45m,
|
386
|
+
# 1.5d, 1h20m, etc.)
|
387
|
+
#
|
388
|
+
# @return (Integer) seconds
|
389
|
+
#
|
319
390
|
def chronify_qty(qty)
|
320
391
|
minutes = 0
|
321
392
|
if qty.strip =~ /^(\d+):(\d\d)$/
|
@@ -339,14 +410,30 @@ class WWID
|
|
339
410
|
minutes * 60
|
340
411
|
end
|
341
412
|
|
413
|
+
##
|
414
|
+
## @brief List sections
|
415
|
+
##
|
416
|
+
## @return (Array) section titles
|
417
|
+
##
|
342
418
|
def sections
|
343
419
|
@content.keys
|
344
420
|
end
|
345
421
|
|
422
|
+
##
|
423
|
+
## @brief Adds a section.
|
424
|
+
##
|
425
|
+
## @param title (String) The new section title
|
426
|
+
##
|
346
427
|
def add_section(title)
|
347
428
|
@content[title.cap_first] = {'original' => "#{title}:", 'items' => []}
|
348
429
|
end
|
349
430
|
|
431
|
+
##
|
432
|
+
## @brief Attempt to match a string with an existing section
|
433
|
+
##
|
434
|
+
## @param frag (String) The user-provided string
|
435
|
+
## @param guessed (Boolean) already guessed and failed
|
436
|
+
##
|
350
437
|
def guess_section(frag,guessed=false)
|
351
438
|
return "All" if frag =~ /all/i
|
352
439
|
sections.each {|section| return section.cap_first if frag.downcase == section.downcase }
|
@@ -377,6 +464,12 @@ class WWID
|
|
377
464
|
section ? section.cap_first : guessed
|
378
465
|
end
|
379
466
|
|
467
|
+
##
|
468
|
+
## @brief Attempt to match a string with an existing view
|
469
|
+
##
|
470
|
+
## @param frag (String) The user-provided string
|
471
|
+
## @param guessed (Boolean) already guessed
|
472
|
+
##
|
380
473
|
def guess_view(frag,guessed=false)
|
381
474
|
views.each {|view| return view if frag.downcase == view.downcase}
|
382
475
|
view = false
|
@@ -399,6 +492,13 @@ class WWID
|
|
399
492
|
view
|
400
493
|
end
|
401
494
|
|
495
|
+
##
|
496
|
+
## @brief Adds an entry
|
497
|
+
##
|
498
|
+
## @param title (String) The entry title
|
499
|
+
## @param section (String) The section to add to
|
500
|
+
## @param opt (Hash) Additional Options {:date, :note, :back, :timed}
|
501
|
+
##
|
402
502
|
def add_item(title,section=nil,opt={})
|
403
503
|
section ||= @current_section
|
404
504
|
add_section(section) unless @content.has_key?(section)
|
@@ -443,6 +543,12 @@ class WWID
|
|
443
543
|
@results.push(%Q{Added "#{entry['title']}" to #{section}})
|
444
544
|
end
|
445
545
|
|
546
|
+
##
|
547
|
+
## @brief Return the content of the last note for a given section
|
548
|
+
##
|
549
|
+
## @param section (String) The section to retrieve from, default
|
550
|
+
## Currently
|
551
|
+
##
|
446
552
|
def last_note(section=@current_section)
|
447
553
|
section = guess_section(section)
|
448
554
|
if @content.has_key?(section)
|
@@ -454,6 +560,11 @@ class WWID
|
|
454
560
|
end
|
455
561
|
end
|
456
562
|
|
563
|
+
##
|
564
|
+
## @brief Tag the last entry or X entries
|
565
|
+
##
|
566
|
+
## @param opt (Hash) Additional Options
|
567
|
+
##
|
457
568
|
def tag_last(opt={})
|
458
569
|
opt[:section] ||= @current_section
|
459
570
|
opt[:count] ||= 1
|
@@ -555,6 +666,13 @@ class WWID
|
|
555
666
|
write(@doing_file)
|
556
667
|
end
|
557
668
|
|
669
|
+
##
|
670
|
+
## @brief Add a note to the last entry in a section
|
671
|
+
##
|
672
|
+
## @param section (String) The section, default Currently
|
673
|
+
## @param note (String) The note to add
|
674
|
+
## @param replace (Bool) Should replace existing note
|
675
|
+
##
|
558
676
|
def note_last(section, note, replace=false)
|
559
677
|
section = guess_section(section)
|
560
678
|
|
@@ -590,10 +708,17 @@ class WWID
|
|
590
708
|
end
|
591
709
|
end
|
592
710
|
|
593
|
-
|
594
|
-
#
|
595
|
-
#
|
596
|
-
#
|
711
|
+
|
712
|
+
#
|
713
|
+
# @brief Accepts one tag and the raw text of a new item if the passed tag
|
714
|
+
# is on any item, it's replaced with @done. if new_item is not
|
715
|
+
# nil, it's tagged with the passed tag and inserted. This is for
|
716
|
+
# use where only one instance of a given tag should exist
|
717
|
+
# (@meanwhile)
|
718
|
+
#
|
719
|
+
# @param tag (String) Tag to replace
|
720
|
+
# @param opt (Hash) Additional Options
|
721
|
+
#
|
597
722
|
def stop_start(tag,opt={})
|
598
723
|
opt[:section] ||= @current_section
|
599
724
|
opt[:archive] ||= false
|
@@ -645,6 +770,11 @@ class WWID
|
|
645
770
|
write(@doing_file)
|
646
771
|
end
|
647
772
|
|
773
|
+
##
|
774
|
+
## @brief Write content to file or STDOUT
|
775
|
+
##
|
776
|
+
## @param file (String) The filepath to write to
|
777
|
+
##
|
648
778
|
def write(file=nil)
|
649
779
|
unless @other_content_top
|
650
780
|
output = ""
|
@@ -670,6 +800,11 @@ class WWID
|
|
670
800
|
end
|
671
801
|
end
|
672
802
|
|
803
|
+
##
|
804
|
+
## @brief Restore a backed up version of a file
|
805
|
+
##
|
806
|
+
## @param file (String) The filepath to restore
|
807
|
+
##
|
673
808
|
def restore_backup(file)
|
674
809
|
if File.exists?(file+"~")
|
675
810
|
puts file+"~"
|
@@ -679,6 +814,11 @@ class WWID
|
|
679
814
|
end
|
680
815
|
|
681
816
|
|
817
|
+
##
|
818
|
+
## @brief Generate a menu of sections and allow user selection
|
819
|
+
##
|
820
|
+
## @return (String) The selected section name
|
821
|
+
##
|
682
822
|
def choose_section
|
683
823
|
sections.each_with_index {|section, i|
|
684
824
|
puts "% 3d: %s" % [i+1, section]
|
@@ -689,10 +829,20 @@ class WWID
|
|
689
829
|
return sections[num.to_i - 1]
|
690
830
|
end
|
691
831
|
|
832
|
+
##
|
833
|
+
## @brief List available views
|
834
|
+
##
|
835
|
+
## @return (Array) View names
|
836
|
+
##
|
692
837
|
def views
|
693
838
|
@config.has_key?('views') ? @config['views'].keys : []
|
694
839
|
end
|
695
840
|
|
841
|
+
##
|
842
|
+
## @brief Generate a menu of views and allow user selection
|
843
|
+
##
|
844
|
+
## @return (String) The selected view name
|
845
|
+
##
|
696
846
|
def choose_view
|
697
847
|
views.each_with_index {|view, i|
|
698
848
|
puts "% 3d: %s" % [i+1, view]
|
@@ -703,6 +853,11 @@ class WWID
|
|
703
853
|
return views[num.to_i - 1]
|
704
854
|
end
|
705
855
|
|
856
|
+
##
|
857
|
+
## @brief Gets a view from configuration
|
858
|
+
##
|
859
|
+
## @param title (String) The title of the view to retrieve
|
860
|
+
##
|
706
861
|
def get_view(title)
|
707
862
|
if @config['views'].has_key?(title)
|
708
863
|
return @config['views'][title]
|
@@ -710,6 +865,12 @@ class WWID
|
|
710
865
|
false
|
711
866
|
end
|
712
867
|
|
868
|
+
##
|
869
|
+
## @brief Overachieving function for displaying contents of a section.
|
870
|
+
## This is a fucking mess. I mean, Jesus Christ.
|
871
|
+
##
|
872
|
+
## @param opt (Hash) Additional Options
|
873
|
+
##
|
713
874
|
def list_section(opt={})
|
714
875
|
opt[:count] ||= 0
|
715
876
|
count = opt[:count] - 1
|
@@ -1084,6 +1245,16 @@ EOTEMPLATE
|
|
1084
1245
|
return out
|
1085
1246
|
end
|
1086
1247
|
|
1248
|
+
##
|
1249
|
+
## @brief Move entries from a section to Archive or other specified
|
1250
|
+
## section
|
1251
|
+
##
|
1252
|
+
## @param section (String) The source section
|
1253
|
+
## @param count (Integer) The count
|
1254
|
+
## @param destination (String) The destination section
|
1255
|
+
## @param tags (Array) Tags to archive
|
1256
|
+
## @param bool (String) Tag boolean combinator
|
1257
|
+
##
|
1087
1258
|
def archive(section="Currently",count=5,destination=nil,tags=nil,bool=nil,export=nil)
|
1088
1259
|
|
1089
1260
|
section = choose_section if section.nil? || section =~ /choose/i
|
@@ -1113,6 +1284,13 @@ EOTEMPLATE
|
|
1113
1284
|
end
|
1114
1285
|
end
|
1115
1286
|
|
1287
|
+
##
|
1288
|
+
## @brief Helper function, performs the actual archiving
|
1289
|
+
##
|
1290
|
+
## @param section (String) The source section
|
1291
|
+
## @param destination (String) The destination section
|
1292
|
+
## @param opt (Hash) Additional Options
|
1293
|
+
##
|
1116
1294
|
def do_archive(section, destination, opt={})
|
1117
1295
|
count = opt[:count] || 5
|
1118
1296
|
tags = opt[:tags] || []
|
@@ -1176,6 +1354,11 @@ EOTEMPLATE
|
|
1176
1354
|
end
|
1177
1355
|
end
|
1178
1356
|
|
1357
|
+
##
|
1358
|
+
## @brief A dictionary of colors
|
1359
|
+
##
|
1360
|
+
## @return (String) ANSI escape sequence
|
1361
|
+
##
|
1179
1362
|
def colors
|
1180
1363
|
color = {}
|
1181
1364
|
color['black'] = "\033[0;0;30m"
|
@@ -1221,18 +1404,28 @@ EOTEMPLATE
|
|
1221
1404
|
end
|
1222
1405
|
|
1223
1406
|
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1407
|
+
##
|
1408
|
+
## @brief Show all entries from the current day
|
1409
|
+
##
|
1410
|
+
## @param times (Boolean) show times
|
1411
|
+
## @param output (String) output format
|
1412
|
+
## @param opt (Hash) Options
|
1413
|
+
##
|
1230
1414
|
def today(times=true,output=nil,opt={})
|
1231
1415
|
opt[:totals] ||= false
|
1232
1416
|
cfg = @config['templates']['today']
|
1233
|
-
list_section({:section =>
|
1417
|
+
list_section({:section => opt[:section], :wrap_width => cfg['wrap_width'], :count => 0, :format => cfg['date_format'], :template => cfg['template'], :order => "asc", :today => true, :times => times, :output => output, :totals => opt[:totals]})
|
1234
1418
|
end
|
1235
1419
|
|
1420
|
+
##
|
1421
|
+
## @brief Display entries within a date range
|
1422
|
+
##
|
1423
|
+
## @param dates (Array) [start, end]
|
1424
|
+
## @param section (String) The section
|
1425
|
+
## @param times (Bool) Show times
|
1426
|
+
## @param output (String) Output format
|
1427
|
+
## @param opt (Hash) Additional Options
|
1428
|
+
##
|
1236
1429
|
def list_date(dates,section,times=nil,output=nil,opt={})
|
1237
1430
|
opt[:totals] ||= false
|
1238
1431
|
section = guess_section(section)
|
@@ -1244,12 +1437,27 @@ EOTEMPLATE
|
|
1244
1437
|
list_section({:section => section, :count => 0, :order => "asc", :date_filter => dates, :times => times, :output => output, :totals => opt[:totals] })
|
1245
1438
|
end
|
1246
1439
|
|
1440
|
+
##
|
1441
|
+
## @brief Show entries from the previous day
|
1442
|
+
##
|
1443
|
+
## @param section (String) The section
|
1444
|
+
## @param times (Bool) Show times
|
1445
|
+
## @param output (String) Output format
|
1446
|
+
## @param opt (Hash) Additional Options
|
1447
|
+
##
|
1247
1448
|
def yesterday(section,times=nil,output=nil,opt={})
|
1248
1449
|
opt[:totals] ||= false
|
1249
1450
|
section = guess_section(section)
|
1250
1451
|
list_section({:section => section, :count => 0, :order => "asc", :yesterday => true, :times => times, :output => output, :totals => opt[:totals] })
|
1251
1452
|
end
|
1252
1453
|
|
1454
|
+
##
|
1455
|
+
## @brief Show recent entries
|
1456
|
+
##
|
1457
|
+
## @param count (Integer) The number to show
|
1458
|
+
## @param section (String) The section to show from, default Currently
|
1459
|
+
## @param opt (Hash) Additional Options
|
1460
|
+
##
|
1253
1461
|
def recent(count=10,section=nil,opt={})
|
1254
1462
|
times = opt[:t] || true
|
1255
1463
|
opt[:totals] ||= false
|
@@ -1259,6 +1467,12 @@ EOTEMPLATE
|
|
1259
1467
|
list_section({:section => section, :wrap_width => cfg['wrap_width'], :count => count, :format => cfg['date_format'], :template => cfg['template'], :order => "asc", :times => times, :totals => opt[:totals] })
|
1260
1468
|
end
|
1261
1469
|
|
1470
|
+
##
|
1471
|
+
## @brief Show the last entry
|
1472
|
+
##
|
1473
|
+
## @param times (Bool) Show times
|
1474
|
+
## @param section (String) Section to pull from, default Currently
|
1475
|
+
##
|
1262
1476
|
def last(times=true,section=nil)
|
1263
1477
|
section ||= @current_section
|
1264
1478
|
section = guess_section(section)
|
@@ -1266,6 +1480,11 @@ EOTEMPLATE
|
|
1266
1480
|
list_section({:section => section, :wrap_width => cfg['wrap_width'], :count => 1, :format => cfg['date_format'], :template => cfg['template'], :times => times})
|
1267
1481
|
end
|
1268
1482
|
|
1483
|
+
##
|
1484
|
+
## @brief Get total elapsed time for all tags in selection
|
1485
|
+
##
|
1486
|
+
## @param format (String) return format (html, json, or text)
|
1487
|
+
##
|
1269
1488
|
def tag_times(format="text")
|
1270
1489
|
|
1271
1490
|
return "" if @timers.empty?
|
@@ -1335,39 +1554,43 @@ EOS
|
|
1335
1554
|
end
|
1336
1555
|
end
|
1337
1556
|
|
1338
|
-
# Uses autotag
|
1339
|
-
# Does not repeat tags in a title, and only converts the first instance of
|
1340
|
-
#
|
1341
|
-
|
1342
|
-
|
1557
|
+
# @brief Uses 'autotag' configuration to turn keywords into tags for time tracking.
|
1558
|
+
# Does not repeat tags in a title, and only converts the first instance of an
|
1559
|
+
# untagged keyword
|
1560
|
+
#
|
1561
|
+
# @param text (String) The text to tag
|
1562
|
+
#
|
1563
|
+
def autotag(text)
|
1564
|
+
return unless text
|
1343
1565
|
@config['autotag']['whitelist'].each {|tag|
|
1344
|
-
|
1566
|
+
text.sub!(/(?<!@)(#{tag.strip})\b/i) do |m|
|
1345
1567
|
m.downcase! if tag =~ /[a-z]/
|
1346
1568
|
"@#{m}"
|
1347
|
-
end unless
|
1569
|
+
end unless text =~ /@#{tag}\b/i
|
1348
1570
|
}
|
1349
1571
|
tail_tags = []
|
1350
1572
|
@config['autotag']['synonyms'].each {|tag, v|
|
1351
1573
|
v.each {|word|
|
1352
|
-
if
|
1574
|
+
if text =~ /\b#{word}\b/i
|
1353
1575
|
tail_tags.push(tag)
|
1354
1576
|
end
|
1355
1577
|
}
|
1356
1578
|
}
|
1357
1579
|
if tail_tags.length > 0
|
1358
|
-
|
1580
|
+
text + ' ' + tail_tags.uniq.map {|t| '@'+t }.join(' ')
|
1359
1581
|
else
|
1360
|
-
|
1582
|
+
text
|
1361
1583
|
end
|
1362
1584
|
end
|
1363
1585
|
|
1364
|
-
def autotag_item(item)
|
1365
|
-
item['title'] = autotag(item['title'])
|
1366
|
-
item
|
1367
|
-
end
|
1368
|
-
|
1369
1586
|
private
|
1370
1587
|
|
1588
|
+
##
|
1589
|
+
## @brief Gets the interval between entry's start date and @done date
|
1590
|
+
##
|
1591
|
+
## @param item (Hash) The entry
|
1592
|
+
## @param formatted (Bool) Return human readable time (default seconds)
|
1593
|
+
##
|
1371
1594
|
def get_interval(item, formatted=true)
|
1372
1595
|
done = nil
|
1373
1596
|
start = nil
|
@@ -1400,6 +1623,11 @@ EOS
|
|
1400
1623
|
seconds > 0 ? "%02d:%02d:%02d" % fmt_time(seconds) : false
|
1401
1624
|
end
|
1402
1625
|
|
1626
|
+
##
|
1627
|
+
## @brief Format human readable time from seconds
|
1628
|
+
##
|
1629
|
+
## @param seconds The seconds
|
1630
|
+
##
|
1403
1631
|
def fmt_time(seconds)
|
1404
1632
|
if seconds.nil?
|
1405
1633
|
return [0, 0, 0]
|