doing 2.0.25 → 2.1.0pre
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/.yardoc/checksums +18 -15
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile.lock +8 -1
- data/README.md +1 -1
- data/Rakefile +23 -4
- data/bin/doing +205 -127
- data/doc/Array.html +354 -1
- data/doc/Doing/Color.html +104 -92
- data/doc/Doing/Completion.html +216 -0
- data/doc/Doing/Configuration.html +340 -5
- data/doc/Doing/Content.html +229 -0
- data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/doc/Doing/Errors/EmptyInput.html +1 -1
- data/doc/Doing/Errors/NoResults.html +1 -1
- data/doc/Doing/Errors/PluginException.html +1 -1
- data/doc/Doing/Errors/UserCancelled.html +1 -1
- data/doc/Doing/Errors/WrongCommand.html +1 -1
- data/doc/Doing/Errors.html +1 -1
- data/doc/Doing/Hooks.html +1 -1
- data/doc/Doing/Item.html +337 -49
- data/doc/Doing/Items.html +444 -35
- data/doc/Doing/LogAdapter.html +139 -51
- data/doc/Doing/Note.html +253 -22
- data/doc/Doing/Pager.html +74 -36
- data/doc/Doing/Plugins.html +1 -1
- data/doc/Doing/Prompt.html +674 -0
- data/doc/Doing/Section.html +354 -0
- data/doc/Doing/Util.html +57 -1
- data/doc/Doing/WWID.html +477 -670
- data/doc/Doing/WWIDFile.html +398 -0
- data/doc/Doing.html +5 -5
- data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/doc/GLI/Commands.html +1 -1
- data/doc/GLI.html +1 -1
- data/doc/Hash.html +97 -1
- data/doc/Status.html +37 -3
- data/doc/String.html +599 -23
- data/doc/Symbol.html +3 -3
- data/doc/Time.html +1 -1
- data/doc/_index.html +22 -1
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +8 -2
- data/doc/index.html +8 -2
- data/doc/method_list.html +453 -173
- data/doc/top-level-namespace.html +1 -1
- data/doing.gemspec +3 -0
- data/doing.rdoc +40 -12
- data/example_plugin.rb +3 -3
- data/lib/completion/_doing.zsh +1 -1
- data/lib/completion/doing.bash +8 -8
- data/lib/completion/doing.fish +1 -1
- data/lib/doing/array.rb +36 -0
- data/lib/doing/colors.rb +70 -66
- data/lib/doing/completion.rb +6 -0
- data/lib/doing/configuration.rb +69 -28
- data/lib/doing/hash.rb +37 -0
- data/lib/doing/item.rb +77 -12
- data/lib/doing/items.rb +125 -0
- data/lib/doing/log_adapter.rb +55 -3
- data/lib/doing/note.rb +53 -1
- data/lib/doing/pager.rb +49 -38
- data/lib/doing/plugins/export/markdown_export.rb +4 -4
- data/lib/doing/plugins/export/template_export.rb +2 -2
- data/lib/doing/plugins/import/calendar_import.rb +4 -4
- data/lib/doing/plugins/import/doing_import.rb +5 -7
- data/lib/doing/plugins/import/timing_import.rb +3 -3
- data/lib/doing/prompt.rb +206 -0
- data/lib/doing/section.rb +30 -0
- data/lib/doing/string.rb +103 -27
- data/lib/doing/util.rb +14 -6
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +306 -621
- data/lib/doing.rb +6 -2
- data/lib/examples/plugins/capture_thing_import.rb +162 -0
- metadata +73 -5
- data/lib/doing/wwidfile.rb +0 -117
data/lib/doing/hash.rb
CHANGED
@@ -27,5 +27,42 @@ module Doing
|
|
27
27
|
def symbolize_keys
|
28
28
|
each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
|
29
29
|
end
|
30
|
+
|
31
|
+
# Set a nested hash value using an array
|
32
|
+
#
|
33
|
+
# @example `{}.deep_set(['one', 'two'], 'value')`
|
34
|
+
# @example `=> { 'one' => { 'two' => 'value' } }
|
35
|
+
#
|
36
|
+
# @param path [Array] key path
|
37
|
+
# @param value The value
|
38
|
+
#
|
39
|
+
def deep_set(path, value)
|
40
|
+
if path.count == 1
|
41
|
+
if value
|
42
|
+
self[path[0]] = value
|
43
|
+
else
|
44
|
+
delete(path[0])
|
45
|
+
end
|
46
|
+
else
|
47
|
+
if value
|
48
|
+
self.default_proc = ->(h, k) { h[k] = Hash.new(&h.default_proc) }
|
49
|
+
dig(*path[0..-2])[path.fetch(-1)] = value
|
50
|
+
else
|
51
|
+
return self unless dig(*path)
|
52
|
+
|
53
|
+
dig(*path[0..-2]).delete(path.fetch(-1))
|
54
|
+
path.pop
|
55
|
+
cleaned = self
|
56
|
+
path.each do |key|
|
57
|
+
if cleaned[key].empty?
|
58
|
+
cleaned.delete(key)
|
59
|
+
break
|
60
|
+
end
|
61
|
+
cleaned = cleaned[key]
|
62
|
+
end
|
63
|
+
empty? ? nil : self
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
30
67
|
end
|
31
68
|
end
|
data/lib/doing/item.rb
CHANGED
@@ -5,10 +5,10 @@ module Doing
|
|
5
5
|
## This class describes a single WWID item
|
6
6
|
##
|
7
7
|
class Item
|
8
|
-
# include Amatch
|
9
|
-
|
10
8
|
attr_accessor :date, :title, :section, :note
|
11
9
|
|
10
|
+
attr_reader :id
|
11
|
+
|
12
12
|
##
|
13
13
|
## Initialize an item with date, title, section, and
|
14
14
|
## optional note
|
@@ -50,8 +50,11 @@ module Doing
|
|
50
50
|
@end_date ||= Time.parse(Regexp.last_match(1)) if @title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
51
51
|
end
|
52
52
|
|
53
|
-
|
54
|
-
|
53
|
+
# Generate a hash that represents the entry
|
54
|
+
#
|
55
|
+
# @return [String] entry hash
|
56
|
+
def id
|
57
|
+
@id ||= (@date.to_s + @title + @section).hash
|
55
58
|
end
|
56
59
|
|
57
60
|
##
|
@@ -105,14 +108,42 @@ module Doing
|
|
105
108
|
##
|
106
109
|
## Add (or remove) tags from the title of the item
|
107
110
|
##
|
108
|
-
## @param
|
109
|
-
## @param
|
110
|
-
##
|
111
|
-
## @
|
112
|
-
## @
|
111
|
+
## @param tags [Array] The tags to apply
|
112
|
+
## @param **options Additional options
|
113
|
+
##
|
114
|
+
## @option options :date [Boolean] Include timestamp?
|
115
|
+
## @option options :single [Boolean] Log as a single change?
|
116
|
+
## @option options :value [String] A value to include as @tag(value)
|
117
|
+
## @option options :remove [Boolean] if true remove instead of adding
|
118
|
+
## @option options :rename_to [String] if not nil, rename target tag to this tag name
|
119
|
+
## @option options :regex [Boolean] treat target tag string as regex pattern
|
120
|
+
## @option options :force [Boolean] with rename_to, add tag if it doesn't exist
|
113
121
|
##
|
114
|
-
def tag(
|
115
|
-
|
122
|
+
def tag(tags, **options)
|
123
|
+
added = []
|
124
|
+
removed = []
|
125
|
+
|
126
|
+
date = options.fetch(:date, false)
|
127
|
+
options[:value] ||= date ? Time.now.strftime('%F %R') : nil
|
128
|
+
options.delete(:date)
|
129
|
+
|
130
|
+
single = options.fetch(:single, false)
|
131
|
+
options.delete(:single)
|
132
|
+
|
133
|
+
tags = tags.to_tags if tags.is_a? ::String
|
134
|
+
|
135
|
+
remove = options.fetch(:remove, false)
|
136
|
+
tags.each do |tag|
|
137
|
+
bool = remove ? :and : :not
|
138
|
+
if tags?(tag, bool)
|
139
|
+
@title.tag!(tag, **options).strip!
|
140
|
+
remove ? removed.push(tag) : added.push(tag)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
Doing.logger.log_change(tags_added: added, tags_removed: removed, count: 1, item: self, single: single)
|
145
|
+
|
146
|
+
self
|
116
147
|
end
|
117
148
|
|
118
149
|
##
|
@@ -121,7 +152,7 @@ module Doing
|
|
121
152
|
## @return [Array] array of tags (no values)
|
122
153
|
##
|
123
154
|
def tags
|
124
|
-
@title.scan(/(?<= |\A)@([^\s(]+)/).map {|tag| tag[0]}.sort.uniq
|
155
|
+
@title.scan(/(?<= |\A)@([^\s(]+)/).map { |tag| tag[0] }.sort.uniq
|
125
156
|
end
|
126
157
|
|
127
158
|
##
|
@@ -190,6 +221,40 @@ module Doing
|
|
190
221
|
should?('never_time')
|
191
222
|
end
|
192
223
|
|
224
|
+
##
|
225
|
+
## Move item from current section to destination section
|
226
|
+
##
|
227
|
+
## @param new_section [String] The destination
|
228
|
+
## section
|
229
|
+
## @param label [Boolean] add @from(original
|
230
|
+
## section) tag
|
231
|
+
## @param log [Boolean] log this action
|
232
|
+
##
|
233
|
+
## @return nothing
|
234
|
+
##
|
235
|
+
def move_to(new_section, label: true, log: true)
|
236
|
+
from = @section
|
237
|
+
|
238
|
+
tag('from', rename_to: 'from', value: from, force: true) if label
|
239
|
+
@section = new_section
|
240
|
+
|
241
|
+
Doing.logger.count(@section == 'Archive' ? :archived : :moved) if log
|
242
|
+
Doing.logger.debug("#{@section == 'Archive' ? 'Archived' : 'Moved'}:",
|
243
|
+
"#{@title.truncate(60)} from #{from} to #{@section}")
|
244
|
+
self
|
245
|
+
end
|
246
|
+
|
247
|
+
# outputs item in Doing file format, including leading tab
|
248
|
+
def to_s
|
249
|
+
"\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title}#{@note.empty? ? '' : "\n#{@note}"}"
|
250
|
+
end
|
251
|
+
|
252
|
+
# @private
|
253
|
+
def inspect
|
254
|
+
# %(<Doing::Item @date=#{@date} @title="#{@title}" @section:"#{@section}" @note:#{@note.to_s}>)
|
255
|
+
%(<Doing::Item @date=#{@date}>)
|
256
|
+
end
|
257
|
+
|
193
258
|
private
|
194
259
|
|
195
260
|
def should?(key)
|
data/lib/doing/items.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Items Array
|
5
|
+
class Items < Array
|
6
|
+
attr_accessor :sections
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
@sections = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# List sections, title only
|
14
|
+
#
|
15
|
+
# @return [Array] section titles
|
16
|
+
#
|
17
|
+
def section_titles
|
18
|
+
@sections.map(&:title)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Test if section already exists
|
22
|
+
#
|
23
|
+
# @param section [String] section title
|
24
|
+
#
|
25
|
+
# @return [Boolean] true if section exists
|
26
|
+
#
|
27
|
+
def section?(section)
|
28
|
+
has_section = false
|
29
|
+
section = section.is_a?(Section) ? section.title.downcase : section.downcase
|
30
|
+
@sections.each do |s|
|
31
|
+
if s.title.downcase == section
|
32
|
+
has_section = true
|
33
|
+
break
|
34
|
+
end
|
35
|
+
end
|
36
|
+
has_section
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add a new section to the sections array. Accepts
|
40
|
+
# either a Section object, or a title string that will
|
41
|
+
# be converted into a Section.
|
42
|
+
#
|
43
|
+
# @param section [Section] The section to add. A
|
44
|
+
# String value will be converted to
|
45
|
+
# Section automatically.
|
46
|
+
# @param log [Boolean] Add a log message
|
47
|
+
# notifying the user about the
|
48
|
+
# creation of the section.
|
49
|
+
#
|
50
|
+
# @return nothing
|
51
|
+
#
|
52
|
+
def add_section(section, log: false)
|
53
|
+
section = section.is_a?(Section) ? section : Section.new(section.cap_first)
|
54
|
+
|
55
|
+
return if section?(section)
|
56
|
+
|
57
|
+
@sections.push(section)
|
58
|
+
Doing.logger.info('New section:', %("#{section}" added)) if log
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get a new Items object containing only items in a
|
62
|
+
# specified section
|
63
|
+
#
|
64
|
+
# @param section [String] section title
|
65
|
+
#
|
66
|
+
# @return [Items] Array of items
|
67
|
+
#
|
68
|
+
def in_section(section)
|
69
|
+
if section =~ /^all$/i
|
70
|
+
dup
|
71
|
+
else
|
72
|
+
items = Items.new.concat(select { |item| item.section == section })
|
73
|
+
items.add_section(section, log: false)
|
74
|
+
items
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
## Delete an item from the index
|
80
|
+
##
|
81
|
+
## @param item The item
|
82
|
+
##
|
83
|
+
def delete_item(item, single: false)
|
84
|
+
deleted = delete(item)
|
85
|
+
Doing.logger.count(:deleted)
|
86
|
+
Doing.logger.info('Entry deleted:', deleted.title) if single
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
## Update an item in the index with a modified item
|
91
|
+
##
|
92
|
+
## @param old_item The old item
|
93
|
+
## @param new_item The new item
|
94
|
+
##
|
95
|
+
def update_item(old_item, new_item)
|
96
|
+
s_idx = index { |item| item.equal?(old_item) }
|
97
|
+
|
98
|
+
raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx
|
99
|
+
|
100
|
+
return if fetch(s_idx).equal?(new_item)
|
101
|
+
|
102
|
+
self[s_idx] = new_item
|
103
|
+
Doing.logger.count(:updated)
|
104
|
+
Doing.logger.info('Entry updated:', self[s_idx].title.truncate(60))
|
105
|
+
new_item
|
106
|
+
end
|
107
|
+
|
108
|
+
# Output sections and items in Doing file format
|
109
|
+
def to_s
|
110
|
+
out = []
|
111
|
+
@sections.each do |section|
|
112
|
+
out.push(section.original)
|
113
|
+
in_section(section.title).each { |item| out.push(item.to_s)}
|
114
|
+
end
|
115
|
+
|
116
|
+
out.join("\n")
|
117
|
+
end
|
118
|
+
|
119
|
+
# @private
|
120
|
+
def inspect
|
121
|
+
"#<Doing::Items #{count} items, #{@sections.count} sections: #{@sections.map { |s| "<Section:#{s.title} #{in_section(s.title).count} items>" }.join(', ')}>"
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
data/lib/doing/log_adapter.rb
CHANGED
@@ -5,9 +5,16 @@ module Doing
|
|
5
5
|
## Log adapter
|
6
6
|
##
|
7
7
|
class LogAdapter
|
8
|
-
|
8
|
+
# Sets the log device
|
9
|
+
attr_writer :logdev
|
9
10
|
|
10
|
-
|
11
|
+
# Max length of log messages (truncate in middle)
|
12
|
+
attr_writer :max_length
|
13
|
+
|
14
|
+
# Returns the current log level (debug, info, warn, error)
|
15
|
+
attr_reader :level
|
16
|
+
|
17
|
+
attr_reader :messages, :results
|
11
18
|
|
12
19
|
TOPIC_WIDTH = 12
|
13
20
|
|
@@ -28,6 +35,7 @@ module Doing
|
|
28
35
|
deleted
|
29
36
|
moved
|
30
37
|
removed_tags
|
38
|
+
rotated
|
31
39
|
skipped
|
32
40
|
updated
|
33
41
|
].freeze
|
@@ -45,6 +53,7 @@ module Doing
|
|
45
53
|
@logdev = $stderr
|
46
54
|
@max_length = `tput cols`.strip.to_i - 5 || 85
|
47
55
|
self.log_level = level
|
56
|
+
@prev_level = level
|
48
57
|
end
|
49
58
|
|
50
59
|
#
|
@@ -71,6 +80,22 @@ module Doing
|
|
71
80
|
@level = level
|
72
81
|
end
|
73
82
|
|
83
|
+
# Set log level temporarily
|
84
|
+
def temp_level(level)
|
85
|
+
return if level.nil? || level.to_sym == @log_level
|
86
|
+
|
87
|
+
@prev_level = log_level.dup
|
88
|
+
@log_level = level.to_sym
|
89
|
+
end
|
90
|
+
|
91
|
+
# Restore temporary level
|
92
|
+
def restore_level
|
93
|
+
return if @prev_level.nil? || @prev_level == @log_level
|
94
|
+
|
95
|
+
self.log_level = @prev_level
|
96
|
+
@prev_level = nil
|
97
|
+
end
|
98
|
+
|
74
99
|
def adjust_verbosity(options = {})
|
75
100
|
if options[:log_level]
|
76
101
|
self.log_level = options[:log_level].to_sym
|
@@ -229,7 +254,6 @@ module Doing
|
|
229
254
|
##
|
230
255
|
def output_results
|
231
256
|
total_counters
|
232
|
-
|
233
257
|
results = @results.select { |msg| write_message?(msg[:level]) }.uniq
|
234
258
|
|
235
259
|
if @logdev == $stdout
|
@@ -241,10 +265,38 @@ module Doing
|
|
241
265
|
end
|
242
266
|
end
|
243
267
|
|
268
|
+
def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false)
|
269
|
+
if tags_added.empty? && tags_removed.empty?
|
270
|
+
count(:skipped, level: :debug, message: '%count %items with no change', count: count)
|
271
|
+
else
|
272
|
+
if tags_added.empty?
|
273
|
+
count(:skipped, level: :debug, message: 'no tags added to %count %items')
|
274
|
+
elsif single && item
|
275
|
+
added = tags_added.log_tags
|
276
|
+
info('Tagged:',
|
277
|
+
%(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{added} to #{item.title}))
|
278
|
+
else
|
279
|
+
count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items')
|
280
|
+
end
|
281
|
+
|
282
|
+
if tags_removed.empty?
|
283
|
+
count(:skipped, level: :debug, message: 'no tags removed from %count %items')
|
284
|
+
elsif single && item
|
285
|
+
added = tags_added.log_tags
|
286
|
+
info('Untagged:',
|
287
|
+
%(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{added} from #{item.title}))
|
288
|
+
else
|
289
|
+
count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items')
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
244
294
|
private
|
245
295
|
|
246
296
|
def format_counter(key, data)
|
247
297
|
case key
|
298
|
+
when :rotated
|
299
|
+
['Rotated:', data[:message] || 'rotated %count %items']
|
248
300
|
when :autotag
|
249
301
|
['Autotag:', data[:message] || 'autotagged %count %items']
|
250
302
|
when :added_tags
|
data/lib/doing/note.rb
CHANGED
@@ -5,12 +5,27 @@ module Doing
|
|
5
5
|
## This class describes an item note.
|
6
6
|
##
|
7
7
|
class Note < Array
|
8
|
+
|
9
|
+
##
|
10
|
+
## Initializes a new note
|
11
|
+
##
|
12
|
+
## @param note [Array] Initial note, can be string
|
13
|
+
## or array
|
14
|
+
##
|
8
15
|
def initialize(note = [])
|
9
16
|
super()
|
10
17
|
|
11
18
|
add(note) if note
|
12
19
|
end
|
13
20
|
|
21
|
+
##
|
22
|
+
## Add note contents, optionally replacing existing note
|
23
|
+
##
|
24
|
+
## @param note [Array] The note to add, can be
|
25
|
+
## string or array (Note)
|
26
|
+
## @param replace [Boolean] replace existing
|
27
|
+
## content
|
28
|
+
##
|
14
29
|
def add(note, replace: false)
|
15
30
|
clear if replace
|
16
31
|
if note.is_a?(String)
|
@@ -20,11 +35,22 @@ module Doing
|
|
20
35
|
end
|
21
36
|
end
|
22
37
|
|
38
|
+
##
|
39
|
+
## Append an array of strings to note
|
40
|
+
##
|
41
|
+
## @param lines [Array] Array of strings
|
42
|
+
##
|
23
43
|
def append(lines)
|
24
44
|
concat(lines)
|
25
45
|
replace compress
|
26
46
|
end
|
27
47
|
|
48
|
+
##
|
49
|
+
## Append a string to the note content
|
50
|
+
##
|
51
|
+
## @param input [String] The input string,
|
52
|
+
## newlines will be split
|
53
|
+
##
|
28
54
|
def append_string(input)
|
29
55
|
concat(input.split(/\n/).map(&:strip))
|
30
56
|
replace compress
|
@@ -34,6 +60,11 @@ module Doing
|
|
34
60
|
replace compress
|
35
61
|
end
|
36
62
|
|
63
|
+
##
|
64
|
+
## Remove blank lines and comment lines (#)
|
65
|
+
##
|
66
|
+
## @return [Array] compressed array
|
67
|
+
##
|
37
68
|
def compress
|
38
69
|
delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ }
|
39
70
|
end
|
@@ -42,14 +73,35 @@ module Doing
|
|
42
73
|
replace strip_lines
|
43
74
|
end
|
44
75
|
|
76
|
+
##
|
77
|
+
## Remove leading/trailing whitespace for
|
78
|
+
## every line of note
|
79
|
+
##
|
80
|
+
## @return [Array] Stripped note
|
81
|
+
##
|
45
82
|
def strip_lines
|
46
83
|
map(&:strip)
|
47
84
|
end
|
48
85
|
|
86
|
+
##
|
87
|
+
## Note as multi-line string
|
49
88
|
def to_s
|
50
|
-
compress.strip_lines.join("\n")
|
89
|
+
compress.strip_lines.map { |l| "\t\t#{l}" }.join("\n")
|
90
|
+
end
|
91
|
+
|
92
|
+
# @private
|
93
|
+
def inspect
|
94
|
+
"<Doing::Note - characters:#{compress.strip_lines.join(' ').length} lines:#{count}>"
|
51
95
|
end
|
52
96
|
|
97
|
+
##
|
98
|
+
## Test if a note is equal (compare string
|
99
|
+
## representations)
|
100
|
+
##
|
101
|
+
## @param other [Note] The other Note
|
102
|
+
##
|
103
|
+
## @return [Boolean] true if equal
|
104
|
+
##
|
53
105
|
def equal?(other)
|
54
106
|
return false unless other.is_a?(Note)
|
55
107
|
|
data/lib/doing/pager.rb
CHANGED
@@ -1,23 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'pathname'
|
2
3
|
|
3
4
|
module Doing
|
4
5
|
# Pagination
|
5
6
|
module Pager
|
6
7
|
class << self
|
8
|
+
# Boolean determines whether output is paginated
|
7
9
|
def paginate
|
8
10
|
@paginate ||= false
|
9
11
|
end
|
10
12
|
|
13
|
+
# Enable/disable pagination
|
14
|
+
#
|
15
|
+
# @param should_paginate [Boolean] true to paginate
|
11
16
|
def paginate=(should_paginate)
|
12
17
|
@paginate = should_paginate
|
13
18
|
end
|
14
19
|
|
20
|
+
# Page output. If @paginate is false, just dump to
|
21
|
+
# STDOUT
|
22
|
+
#
|
23
|
+
# @param text [String] text to paginate
|
24
|
+
#
|
15
25
|
def page(text)
|
16
26
|
unless @paginate
|
17
27
|
puts text
|
18
28
|
return
|
19
29
|
end
|
20
30
|
|
31
|
+
pager = which_pager
|
32
|
+
Doing.logger.debug('Pager:', "Using #{pager}")
|
33
|
+
|
21
34
|
read_io, write_io = IO.pipe
|
22
35
|
|
23
36
|
input = $stdin
|
@@ -30,10 +43,8 @@ module Doing
|
|
30
43
|
# Wait until we have input before we start the pager
|
31
44
|
IO.select [input]
|
32
45
|
|
33
|
-
pager = which_pager
|
34
|
-
Doing.logger.debug('Pager:', "Using #{pager}")
|
35
46
|
begin
|
36
|
-
exec(pager
|
47
|
+
exec(pager)
|
37
48
|
rescue SystemCallError => e
|
38
49
|
raise Errors::DoingStandardError, "Pager error, #{e}"
|
39
50
|
end
|
@@ -51,44 +62,44 @@ module Doing
|
|
51
62
|
status.success?
|
52
63
|
end
|
53
64
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if f.strip =~ /[ |]/
|
67
|
-
f
|
68
|
-
elsif f == 'most'
|
69
|
-
Doing.logger.warn('most not allowed as pager')
|
70
|
-
false
|
71
|
-
else
|
72
|
-
system "which #{f}", out: File::NULL, err: File::NULL
|
73
|
-
end
|
74
|
-
else
|
75
|
-
false
|
65
|
+
private
|
66
|
+
|
67
|
+
def command_exist?(command)
|
68
|
+
exts = ENV.fetch("PATHEXT", "").split(::File::PATH_SEPARATOR)
|
69
|
+
if Pathname.new(command).absolute?
|
70
|
+
::File.exist?(command) ||
|
71
|
+
exts.any? { |ext| ::File.exist?("#{command}#{ext}")}
|
72
|
+
else
|
73
|
+
ENV.fetch("PATH", "").split(::File::PATH_SEPARATOR).any? do |dir|
|
74
|
+
file = ::File.join(dir, command)
|
75
|
+
::File.exist?(file) ||
|
76
|
+
exts.any? { |ext| ::File.exist?("#{file}#{ext}") }
|
76
77
|
end
|
77
78
|
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def git_pager
|
82
|
+
command_exist?("git") ? `git config --get-all core.pager` : nil
|
83
|
+
end
|
78
84
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
85
|
+
def pagers
|
86
|
+
[ENV['GIT_PAGER'], ENV['PAGER'], git_pager,
|
87
|
+
'bat -p --pager="less -Xr"', 'less -Xr', 'more -r'].compact
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_executable(*commands)
|
91
|
+
execs = commands.empty? ? pagers : commands
|
92
|
+
execs
|
93
|
+
.compact.map(&:strip).reject(&:empty?).uniq
|
94
|
+
.find { |cmd| command_exist?(cmd.split.first) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def exec_available?(*commands)
|
98
|
+
!find_executable(*commands).nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
def which_pager
|
102
|
+
@which_pager ||= find_executable(*pagers)
|
92
103
|
end
|
93
104
|
end
|
94
105
|
end
|
@@ -41,11 +41,11 @@ module Doing
|
|
41
41
|
all_items = []
|
42
42
|
items.each do |i|
|
43
43
|
if String.method_defined? :force_encoding
|
44
|
-
title = i.title.force_encoding('utf-8').link_urls(
|
45
|
-
note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls(
|
44
|
+
title = i.title.force_encoding('utf-8').link_urls(format: :markdown)
|
45
|
+
note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls(format: :markdown) } if i.note
|
46
46
|
else
|
47
|
-
title = i.title.link_urls(
|
48
|
-
note = i.note.map { |line| line.strip.link_urls(
|
47
|
+
title = i.title.link_urls(format: :markdown)
|
48
|
+
note = i.note.map { |line| line.strip.link_urls(format: :markdown) } if i.note
|
49
49
|
end
|
50
50
|
|
51
51
|
title = "#{title} @project(#{i.section})" unless variables[:is_single]
|
@@ -23,10 +23,10 @@ module Doing
|
|
23
23
|
out = ''
|
24
24
|
items.each do |item|
|
25
25
|
if opt[:highlight] && item.title =~ /@#{wwid.config['marker_tag']}\b/i
|
26
|
-
flag = Doing::Color.send(wwid.config['marker_color'])
|
26
|
+
# flag = Doing::Color.send(wwid.config['marker_color'])
|
27
27
|
reset = Doing::Color.default
|
28
28
|
else
|
29
|
-
flag = ''
|
29
|
+
# flag = ''
|
30
30
|
reset = ''
|
31
31
|
end
|
32
32
|
|
@@ -27,7 +27,7 @@ module Doing
|
|
27
27
|
options[:no_overlap] ||= false
|
28
28
|
options[:autotag] ||= wwid.auto_tag
|
29
29
|
|
30
|
-
wwid.add_section(section) unless wwid.content.
|
30
|
+
wwid.content.add_section(section) unless wwid.content.section?(section)
|
31
31
|
|
32
32
|
tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
|
33
33
|
prefix = options[:prefix] || '[Calendar.app]'
|
@@ -59,7 +59,7 @@ module Doing
|
|
59
59
|
title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
|
60
60
|
title.gsub!(/ +/, ' ')
|
61
61
|
title.strip!
|
62
|
-
new_entry =
|
62
|
+
new_entry = Item.new(start_time, title, section)
|
63
63
|
new_entry.note = entry['notes'].split(/\n/).map(&:chomp) if entry.key?('notes')
|
64
64
|
new_items.push(new_entry)
|
65
65
|
end
|
@@ -69,11 +69,11 @@ module Doing
|
|
69
69
|
filtered = total - new_items.count
|
70
70
|
Doing.logger.debug('Skipped:' , %(#{filtered} items that didn't match filter criteria)) if filtered.positive?
|
71
71
|
|
72
|
-
new_items = wwid.dedup(new_items, options[:no_overlap])
|
72
|
+
new_items = wwid.dedup(new_items, no_overlap: options[:no_overlap])
|
73
73
|
dups = filtered - new_items.count
|
74
74
|
Doing.logger.info(%(Skipped #{dups} items with overlapping times)) if dups.positive?
|
75
75
|
|
76
|
-
wwid.content
|
76
|
+
wwid.content.concat(new_items)
|
77
77
|
Doing.logger.info(%(Imported #{new_items.count} items to #{section}))
|
78
78
|
end
|
79
79
|
|