na 1.2.35 → 1.2.37

Sign up to get free protection for your applications and to get access to all the features.
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