na 1.2.35 → 1.2.37

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.
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 = '%parent%action'
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 = '%parent%action'
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
+ '%parent%action'
66
+ else
67
+ '%filename%parent%action'
68
+ end
69
+ elsif NA.find_files(depth: depth).count > 1
70
+ if depth > 1
71
+ '%filename%parent%action'
72
+ else
73
+ '%project%parent%action'
74
+ end
75
+ else
76
+ '%parent%action'
77
+ end
78
+ template += '%note' if notes
79
+
80
+ files.map { |f| NA.notify("{dw}#{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/editor.rb ADDED
@@ -0,0 +1,123 @@
1
+ module NA
2
+ module Editor
3
+ class << self
4
+ def default_editor
5
+ editor ||= ENV['NA_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR']
6
+
7
+ if editor.good? && TTY::Which.exist?(editor)
8
+ return editor
9
+ end
10
+
11
+ notify('No EDITOR environment variable, testing available editors', debug: true)
12
+ editors = %w[vim vi code subl mate mvim nano emacs]
13
+ editors.each do |ed|
14
+ try = TTY::Which.which(ed)
15
+ if try
16
+ notify("Using editor #{try}", debug: true)
17
+ return try
18
+ end
19
+ end
20
+
21
+ notify('{br}No editor found{x}', exit_code: 5)
22
+
23
+ nil
24
+ end
25
+
26
+ def editor_with_args
27
+ args_for_editor(default_editor)
28
+ end
29
+
30
+ def args_for_editor(editor)
31
+ return editor if editor =~ /-\S/
32
+
33
+ args = case editor
34
+ when /^(subl|code|mate)$/
35
+ ['-w']
36
+ when /^(vim|mvim)$/
37
+ ['-f']
38
+ else
39
+ []
40
+ end
41
+ "#{editor} #{args.join(' ')}"
42
+ end
43
+
44
+ ##
45
+ ## Create a process for an editor and wait for the file handle to return
46
+ ##
47
+ ## @param input [String] Text input for editor
48
+ ##
49
+ def fork_editor(input = '', message: :default)
50
+ # raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
51
+
52
+ notify('{br}No EDITOR variable defined in environment{x}', exit_code: 5) if default_editor.nil?
53
+
54
+ tmpfile = Tempfile.new(['na_temp', '.na'])
55
+
56
+ File.open(tmpfile.path, 'w+') do |f|
57
+ f.puts input
58
+ unless message.nil?
59
+ f.puts message == :default ? '# First line is the action, lines after are added as a note' : message
60
+ end
61
+ end
62
+
63
+ pid = Process.fork { system("#{editor_with_args} #{tmpfile.path}") }
64
+
65
+ trap('INT') do
66
+ begin
67
+ Process.kill(9, pid)
68
+ rescue StandardError
69
+ Errno::ESRCH
70
+ end
71
+ tmpfile.unlink
72
+ tmpfile.close!
73
+ exit 0
74
+ end
75
+
76
+ Process.wait(pid)
77
+
78
+ begin
79
+ if $?.exitstatus == 0
80
+ input = IO.read(tmpfile.path)
81
+ else
82
+ exit_now! 'Cancelled'
83
+ end
84
+ ensure
85
+ tmpfile.close
86
+ tmpfile.unlink
87
+ end
88
+
89
+ input.split(/\n/).delete_if(&:ignore?).join("\n")
90
+ end
91
+
92
+ ##
93
+ ## Takes a multi-line string and formats it as an entry
94
+ ##
95
+ ## @param input [String] The string to parse
96
+ ##
97
+ ## @return [Array] [[String]title, [Note]note]
98
+ ##
99
+ def format_input(input)
100
+ notify('No content in entry', exit_code: 1) if input.nil? || input.strip.empty?
101
+
102
+ input_lines = input.split(/[\n\r]+/).delete_if(&:ignore?)
103
+ title = input_lines[0]&.strip
104
+ notify('{br}No content in first line{x}', exit_code: 1) if title.nil? || title.strip.empty?
105
+
106
+ title.expand_date_tags
107
+
108
+ note = if input_lines.length > 1
109
+ input_lines[1..-1]
110
+ else
111
+ []
112
+ end
113
+
114
+ unless note.empty?
115
+ note.map!(&:strip)
116
+ note.delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ }
117
+ end
118
+
119
+ [title, note]
120
+ end
121
+ end
122
+ end
123
+ end