doing 1.0.21 → 1.0.22
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 +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]
|