na 1.2.35 → 1.2.38

Sign up to get free protection for your applications and to get access to all the features.
data/lib/na/action.rb CHANGED
@@ -18,6 +18,32 @@ module NA
18
18
  @note = note
19
19
  end
20
20
 
21
+ def process(priority: 0, finish: false, add_tag: [], remove_tag: [], note: [])
22
+ string = @action.dup
23
+
24
+ if priority&.positive?
25
+ string.gsub!(/(?<=\A| )@priority\(\d+\)/, '').strip!
26
+ string += " @priority(#{priority})"
27
+ end
28
+
29
+ remove_tag.each do |tag|
30
+ string.gsub!(/(?<=\A| )@#{tag.gsub(/([()*?])/, '\\\\1')}(\(.*?\))?/, '')
31
+ string.strip!
32
+ end
33
+
34
+ add_tag.each do |tag|
35
+ string.gsub!(/(?<=\A| )@#{tag.gsub(/([()*?])/, '\\\\1')}(\(.*?\))?/, '')
36
+ string.strip!
37
+ string += " @#{tag}"
38
+ end
39
+
40
+ string = "#{string.strip} @done(#{Time.now.strftime('%Y-%m-%d %H:%M')})" if finish && string !~ /(?<=\A| )@done/
41
+
42
+ @action = string
43
+ @action.expand_date_tags
44
+ @note = note unless note.empty?
45
+ end
46
+
21
47
  def to_s
22
48
  note = if @note.count.positive?
23
49
  "\n#{@note.join("\n")}"
@@ -48,25 +74,14 @@ module NA
48
74
  ## @param notes [Boolean] Include notes
49
75
  ##
50
76
  def pretty(extension: 'taskpaper', template: {}, regexes: [], notes: false)
51
- # Default colorization, can be overridden with full or partial template variable
52
- default_template = {
53
- file: '{xbk}',
54
- parent: '{c}',
55
- parent_divider: '{xw}/',
56
- action: '{bg}',
57
- project: '{xbk}',
58
- tags: '{m}',
59
- value_parens: '{m}',
60
- values: '{y}',
61
- output: '%filename%parents| %action',
62
- note: '{dw}'
63
- }
64
- template = default_template.merge(template)
77
+ theme = NA::Theme.load_theme
78
+ template = theme.merge(template)
79
+
65
80
  # Create the hierarchical parent string
66
81
  parents = @parent.map do |par|
67
- NA::Color.template("#{template[:parent]}#{par}")
82
+ NA::Color.template("{x}#{template[:parent]}#{par}")
68
83
  end.join(NA::Color.template(template[:parent_divider]))
69
- parents = "{dc}[{x}#{parents}{dc}]{x} "
84
+ parents = "#{NA.theme[:bracket]}[#{NA.theme[:error]}#{parents}#{NA.theme[:bracket]}]{x} "
70
85
 
71
86
  # Create the project string
72
87
  project = NA::Color.template("#{template[:project]}#{@project}{x} ")
@@ -75,7 +90,7 @@ module NA
75
90
  file = @file.sub(%r{^\./}, '').sub(/#{ENV['HOME']}/, '~')
76
91
  file = file.sub(/\.#{extension}$/, '')
77
92
  # colorize the basename
78
- file = file.sub(/#{File.basename(@file, ".#{extension}")}$/, "{dw}#{File.basename(@file, ".#{extension}")}{x}")
93
+ file = file.highlight_filename
79
94
  file_tpl = "#{template[:file]}#{file} {x}"
80
95
  filename = NA::Color.template(file_tpl)
81
96
 
@@ -174,13 +189,13 @@ module NA
174
189
  tag_date = Time.parse(tag_val)
175
190
  date = Chronic.parse(val)
176
191
 
192
+ raise ArgumentError if date.nil?
193
+
177
194
  unless val =~ /(\d:\d|a[mp]|now)/i
178
195
  tag_date = Time.parse(tag_date.strftime('%Y-%m-%d 12:00'))
179
196
  date = Time.parse(date.strftime('%Y-%m-%d 12:00'))
180
197
  end
181
198
 
182
- puts "Comparing #{tag_date} #{tag[:comp]} #{date}" if NA.verbose
183
-
184
199
  case tag[:comp]
185
200
  when /^>$/
186
201
  tag_date > date
@@ -201,7 +216,7 @@ module NA
201
216
  else
202
217
  false
203
218
  end
204
- rescue
219
+ rescue ArgumentError
205
220
  case tag[:comp]
206
221
  when /^>$/
207
222
  tag_val.to_f > val.to_f
@@ -213,12 +228,14 @@ module NA
213
228
  tag_val.to_f >= val.to_f
214
229
  when /^==?$/
215
230
  tag_val =~ /^#{val.wildcard_to_rx}$/
231
+ when /^=~$/
232
+ tag_val =~ Regexp.new(val, Regexp::IGNORECASE)
216
233
  when /^\$=$/
217
234
  tag_val =~ /#{val.wildcard_to_rx}$/i
218
235
  when /^\*=$/
219
- tag_val =~ /#{val.wildcard_to_rx}/i
236
+ tag_val =~ /.*?#{val.wildcard_to_rx}.*?/i
220
237
  when /^\^=$/
221
- tag_val =~ /^#{val.wildcard_to_rx}/
238
+ tag_val =~ /^#{val.wildcard_to_rx}/i
222
239
  else
223
240
  false
224
241
  end
data/lib/na/actions.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NA
4
+ # Actions controller
5
+ class Actions < Array
6
+ def initialize(actions = [])
7
+ super
8
+ concat(actions)
9
+ end
10
+
11
+ ##
12
+ ## Pretty print a list of actions
13
+ ##
14
+ ## @param actions [Array] The actions
15
+ ## @param depth [Number] The depth
16
+ ## @param files [Array] The files actions originally came from
17
+ ## @param regexes [Array] The regexes used to gather actions
18
+ ##
19
+ def output(depth, files: nil, regexes: [], notes: false, nest: false, nest_projects: false)
20
+ return if files.nil?
21
+
22
+ if nest
23
+ template = NA.theme[:templates][:default]
24
+
25
+ parent_files = {}
26
+ out = []
27
+
28
+ if nest_projects
29
+ each do |action|
30
+ if parent_files.key?(action.file)
31
+ parent_files[action.file].push(action)
32
+ else
33
+ parent_files[action.file] = [action]
34
+ end
35
+ end
36
+
37
+ parent_files.each do |file, acts|
38
+ projects = NA.project_hierarchy(acts)
39
+ out.push("#{file.sub(%r{^./}, '').shorten_path}:")
40
+ out.concat(NA.output_children(projects, 0))
41
+ end
42
+ else
43
+ template = NA.theme[:templates][:default]
44
+
45
+ each do |action|
46
+ if parent_files.key?(action.file)
47
+ parent_files[action.file].push(action)
48
+ else
49
+ parent_files[action.file] = [action]
50
+ end
51
+ end
52
+
53
+ parent_files.each do |k, v|
54
+ out.push("#{k.sub(%r{^\./}, '')}:")
55
+ v.each do |a|
56
+ out.push("\t- [#{a.parent.join('/')}] #{a.action}")
57
+ out.push("\t\t#{a.note.join("\n\t\t")}") unless a.note.empty?
58
+ end
59
+ end
60
+ end
61
+ NA::Pager.page out.join("\n")
62
+ else
63
+ template = if files.count.positive?
64
+ if files.count == 1
65
+ NA.theme[:templates][:single_file]
66
+ else
67
+ NA.theme[:templates][:multi_file]
68
+ end
69
+ elsif NA.find_files(depth: depth).count > 1
70
+ if depth > 1
71
+ NA.theme[:templates][:multi_file]
72
+ else
73
+ NA.theme[:templates][:single_file]
74
+ end
75
+ else
76
+ NA.theme[:templates][:default]
77
+ end
78
+ template += '%note' if notes
79
+
80
+ files.map { |f| NA.notify(f, debug: true) } if files
81
+
82
+ output = map { |action| action.pretty(template: { output: template }, regexes: regexes, notes: notes) }
83
+ NA::Pager.page(output.join("\n"))
84
+ end
85
+ end
86
+ end
87
+ end
data/lib/na/colors.rb CHANGED
@@ -5,7 +5,7 @@ module NA
5
5
  # Terminal output color functions.
6
6
  module Color
7
7
  # Regexp to match excape sequences
8
- ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
8
+ ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/
9
9
 
10
10
  # All available color names. Available as methods and string extensions.
11
11
  #
@@ -214,9 +214,15 @@ module NA
214
214
  ## m: magenta, r: red, b: bold, u: underline, i: italic,
215
215
  ## x: reset (remove background, color, emphasis)
216
216
  ##
217
+ ## Also accepts {#RGB} and {#RRGGBB} strings. Put a b before
218
+ ## the hash to make it a background color
219
+ ##
217
220
  ## @example Convert a templated string
218
221
  ## Color.template('{Rwb}Warning:{x} {w}you look a little {g}ill{x}')
219
222
  ##
223
+ ## @example Convert using RGB colors
224
+ ## Color.template('{#f0a}This is an RGB color')
225
+ ##
220
226
  ## @param input [String, Array] The template
221
227
  ## string. If this is an array, the
222
228
  ## elements will be joined with a
@@ -228,6 +234,11 @@ module NA
228
234
  input = input.join(' ') if input.is_a? Array
229
235
  return input.gsub(/(?<!\\)\{(\w+)\}/i, '') unless NA::Color.coloring?
230
236
 
237
+ input = input.gsub(/(?<!\\)\{((?:[fb]g?)?#[a-f0-9]{3,6})\}/i) do
238
+ hex = Regexp.last_match(1)
239
+ rgb(hex)
240
+ end
241
+
231
242
  fmt = input.gsub(/%/, '%%')
232
243
  fmt = fmt.gsub(/(?<!\\)\{(\w+)\}/i) do
233
244
  Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
@@ -301,6 +312,17 @@ module NA
301
312
  is_bg = hex.match(/^bg?#/) ? true : false
302
313
  hex_string = hex.sub(/^([fb]g?)?#/, '')
303
314
 
315
+ if hex_string.length == 3
316
+ parts = hex_string.match(/(?<r>.)(?<g>.)(?<b>.)/)
317
+
318
+ t = []
319
+ %w[r g b].each do |e|
320
+ t << parts[e]
321
+ t << parts[e]
322
+ end
323
+ hex_string = t.join('')
324
+ end
325
+
304
326
  parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
305
327
  t = []
306
328
  %w[r g b].each do |e|
data/lib/na/editor.rb ADDED
@@ -0,0 +1,125 @@
1
+ module NA
2
+ module Editor
3
+ class << self
4
+ def default_editor(prefer_git_editor: true)
5
+ if prefer_git_editor
6
+ editor ||= ENV['NA_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR']
7
+ else
8
+ editor ||= ENV['NA_EDITOR'] || ENV['EDITOR'] || ENV['GIT_EDITOR']
9
+ end
10
+
11
+ return editor if editor&.good? && TTY::Which.exist?(editor)
12
+
13
+ NA.notify("No EDITOR environment variable, testing available editors", debug: true)
14
+ editors = %w[vim vi code subl mate mvim nano emacs]
15
+ editors.each do |ed|
16
+ try = TTY::Which.which(ed)
17
+ if try
18
+ NA.notify("Using editor #{try}", debug: true)
19
+ return try
20
+ end
21
+ end
22
+
23
+ NA.notify("#{NA.theme[:error]}No editor found", exit_code: 5)
24
+
25
+ nil
26
+ end
27
+
28
+ def editor_with_args
29
+ args_for_editor(default_editor)
30
+ end
31
+
32
+ def args_for_editor(editor)
33
+ return editor if editor =~ /-\S/
34
+
35
+ args = case editor
36
+ when /^(subl|code|mate)$/
37
+ ['-w']
38
+ when /^(vim|mvim)$/
39
+ ['-f']
40
+ else
41
+ []
42
+ end
43
+ "#{editor} #{args.join(' ')}"
44
+ end
45
+
46
+ ##
47
+ ## Create a process for an editor and wait for the file handle to return
48
+ ##
49
+ ## @param input [String] Text input for editor
50
+ ##
51
+ def fork_editor(input = '', message: :default)
52
+ # raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
53
+
54
+ NA.notify("#{NA.theme[:error]}No EDITOR variable defined in environment", exit_code: 5) if default_editor.nil?
55
+
56
+ tmpfile = Tempfile.new(['na_temp', '.na'])
57
+
58
+ File.open(tmpfile.path, 'w+') do |f|
59
+ f.puts input
60
+ unless message.nil?
61
+ f.puts message == :default ? '# First line is the action, lines after are added as a note' : message
62
+ end
63
+ end
64
+
65
+ pid = Process.fork { system("#{editor_with_args} #{tmpfile.path}") }
66
+
67
+ trap('INT') do
68
+ begin
69
+ Process.kill(9, pid)
70
+ rescue StandardError
71
+ Errno::ESRCH
72
+ end
73
+ tmpfile.unlink
74
+ tmpfile.close!
75
+ exit 0
76
+ end
77
+
78
+ Process.wait(pid)
79
+
80
+ begin
81
+ if $?.exitstatus == 0
82
+ input = IO.read(tmpfile.path)
83
+ else
84
+ exit_now! 'Cancelled'
85
+ end
86
+ ensure
87
+ tmpfile.close
88
+ tmpfile.unlink
89
+ end
90
+
91
+ input.split(/\n/).delete_if(&:ignore?).join("\n")
92
+ end
93
+
94
+ ##
95
+ ## Takes a multi-line string and formats it as an entry
96
+ ##
97
+ ## @param input [String] The string to parse
98
+ ##
99
+ ## @return [Array] [[String]title, [Note]note]
100
+ ##
101
+ def format_input(input)
102
+ NA.notify("#{NA.theme[:error]}No content in entry", exit_code: 1) if input.nil? || input.strip.empty?
103
+
104
+ input_lines = input.split(/[\n\r]+/).delete_if(&:ignore?)
105
+ title = input_lines[0]&.strip
106
+ NA.notify("#{NA.theme[:error]}No content in first line", exit_code: 1) if title.nil? || title.strip.empty?
107
+
108
+ title.expand_date_tags
109
+
110
+ note = if input_lines.length > 1
111
+ input_lines[1..-1]
112
+ else
113
+ []
114
+ end
115
+
116
+ unless note.empty?
117
+ note.map!(&:strip)
118
+ note.delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ }
119
+ end
120
+
121
+ [title, note]
122
+ end
123
+ end
124
+ end
125
+ end
data/lib/na/hash.rb CHANGED
@@ -4,4 +4,35 @@ class ::Hash
4
4
  def symbolize_keys
5
5
  each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
6
6
  end
7
+
8
+ ##
9
+ ## Freeze all values in a hash
10
+ ##
11
+ ## @return Hash with all values frozen
12
+ ##
13
+ def deep_freeze
14
+ chilled = {}
15
+ each do |k, v|
16
+ chilled[k] = v.is_a?(Hash) ? v.deep_freeze : v.freeze
17
+ end
18
+
19
+ chilled.freeze
20
+ end
21
+
22
+ def deep_freeze!
23
+ replace deep_thaw.deep_freeze
24
+ end
25
+
26
+ def deep_thaw
27
+ chilled = {}
28
+ each do |k, v|
29
+ chilled[k] = v.is_a?(Hash) ? v.deep_thaw : v.dup
30
+ end
31
+
32
+ chilled.dup
33
+ end
34
+
35
+ def deep_thaw!
36
+ replace deep_thaw
37
+ end
7
38
  end