howzit 2.0.2 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5758dea871a182fc419de237ba35828396000307710b1680841bb774ea6994a2
4
- data.tar.gz: bdf200a5a60c0acd268e37adcfae6b187807a14766e8199970fbd1d3ac1e4e3d
3
+ metadata.gz: 95a5f6c56ebff1fb944ca8ea23cba45ca5b71647654a315fd2cf7e486d522309
4
+ data.tar.gz: f9ac61f6fad2aff412d4302d0c586e9b287f284659255215a9565c8632bdfc8c
5
5
  SHA512:
6
- metadata.gz: c6ec5058df211c32bdf02a33f99626822221317ee95eb16161d17e4ede96ae31b744a16aee328cead7c4e3bab5ca393f0911227234709622fea27f4026a6504f
7
- data.tar.gz: 7ce5c34e121fa6a3fa6fea662d21b3ccc60beba0e0fee8f0b87b0f8653e3d0e64c66f61cfdc978e0a4b1d425cb757fcf191ba4026262c1ce26225dad8b87897d
6
+ metadata.gz: be7ac525fc55a0e199b5762830bd0c6ef41b0b099627a45f1ca2aacd4508df3574f06e803513a22d870985631520c58b7f75c9e8519b5b02cf274eaeec577d56
7
+ data.tar.gz: e60252f5081ae563fc7eb8c207bb6ba0674b316455247cbc886e1b386dd33ecef234cfa493c6453bc02403daa11f79afb5f514c95a2f4e080302010466a7d4a6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ ### 2.0.5
2
+
3
+ 2022-08-04 10:50
4
+
5
+ #### NEW
6
+
7
+ - Make any task optional when running by adding a ? (@open?(...))
8
+ - Optional tasks default to yes when you hit enter, invert by using a ! (@open?!(...)) to make default "no"
9
+
10
+ #### FIXED
11
+
12
+ - Replace escaped newlines in task list output so that they don't trigger a newline in the shell
13
+
14
+ ### 2.0.4
15
+
16
+ 2022-08-04 06:25
17
+
18
+ #### IMPROVED
19
+
20
+ - Ask to open new buildnote for editing after creation
21
+
22
+ #### FIXED
23
+
24
+ - Loop when creating new buildnote
25
+
26
+ ### 2.0.3
27
+
28
+ 2022-08-04 05:31
29
+
30
+ #### IMPROVED
31
+
32
+ - General code cleanup
33
+ - Attempt at os agnostic @copy command, hopefully works on Windows and Linux systems
34
+
35
+ #### FIXED
36
+
37
+ - Not displaying action if title is missing
38
+
1
39
  ### 2.0.2
2
40
 
3
41
  2022-08-03 18:26
data/README.md CHANGED
@@ -94,6 +94,18 @@ You can include commands that can be executed by howzit. Commands start at the b
94
94
 
95
95
  A block defined between @after and @end markers will be displayed after a topic is run. Use it to remind yourself of additional tasks after automated ones have been executed. The content between these markers is still included when viewing the topic, but the tags themselves do not show up in output.
96
96
 
97
+ ### Adding Titles
98
+
99
+ When displaying a topic, howzit can display a title instead of the contents of a directive. Any text after the closing parenthesis will be considered the task title.
100
+
101
+ ```
102
+ @run(~/scripts/deploy.sh) Deploy script
103
+ ```
104
+
105
+ When the topic is displayed, it will now show "Run Deploy script" instead of the path to the script. This can be overridden with the `--show-code` flag, which will instead show the contents of the directive.
106
+
107
+ For adding titles to run blocks (fenced code), see below.
108
+
97
109
  ### Run blocks (embedded scripts)
98
110
 
99
111
  For longer scripts you can write shell scripts and then call them with `@run(myscript.sh)`. For those in-between cases where you need a few commands but aren't motivated to create a separate file, you can use fenced code blocks with `run` as the language.
@@ -117,6 +129,12 @@ Example:
117
129
 
118
130
  Multiple blocks can be included in a topic. @commands take priority over code blocks and will be run first if they exist in the same topic.
119
131
 
132
+ ### Requiring Confirmation
133
+
134
+ You can have howzit confirm whether to execute an @command at runtime by including a question mark to the directive, e.g. `@run?(...)`. This will present a yes/no dialog before running the directive, with a default response of "yes" if you just hit return. To make the default response "no," add an exclamation point, e.g. `@run?!(...)`.
135
+
136
+ This works for any directive, including `@include`, and run blocks, which can use `run?` as the language specifier.
137
+
120
138
  ### Variables
121
139
 
122
140
  When running commands in a topic, you can use a double dash (`--`) in the command line (surrounded by spaces) and anything after it will be interpreted as shell arguments. These can be used in commands with `$` placeholders. `$1` represents the first argument, counting up from there. Use `$@` to pass all arguments as a shell-escaped string.
data/bin/howzit CHANGED
@@ -115,7 +115,7 @@ OptionParser.new do |opts|
115
115
 
116
116
  parts = key.split(/=/)
117
117
  k = parts[0].sub(/^:/, '')
118
- v = parts[1..-1].join(' ')
118
+ v = parts[1..].join(' ')
119
119
 
120
120
  if Howzit.options.key?(k.to_sym)
121
121
  Howzit.options[k.to_sym] = v.to_config_value(Howzit.options[k.to_sym])
data/howzit.gemspec CHANGED
@@ -40,4 +40,5 @@ Gem::Specification.new do |spec|
40
40
 
41
41
  spec.add_runtime_dependency 'mdless', '~> 1.0', '>= 1.0.28'
42
42
  spec.add_runtime_dependency 'tty-screen', '~> 0.8'
43
+ # spec.add_runtime_dependency 'tty-prompt', '~> 0.23'
43
44
  end
@@ -41,9 +41,9 @@ module Howzit
41
41
  # Output a list of topic titles
42
42
  def list
43
43
  output = []
44
- output.push(Color.template("{bg}Topics:{x}\n"))
44
+ output.push("{bg}Topics:{x}\n".c)
45
45
  @topics.each do |topic|
46
- output.push(Color.template("- {bw}#{topic.title}{x}"))
46
+ output.push("- {bw}#{topic.title}{x}".c)
47
47
  end
48
48
  output.join("\n")
49
49
  end
@@ -66,7 +66,7 @@ module Howzit
66
66
 
67
67
  def list_runnable
68
68
  output = []
69
- output.push(Color.template(%({bg}"Runnable" Topics:{x}\n)))
69
+ output.push(%({bg}"Runnable" Topics:{x}\n).c)
70
70
  @topics.each do |topic|
71
71
  s_out = []
72
72
 
@@ -75,7 +75,7 @@ module Howzit
75
75
  end
76
76
 
77
77
  unless s_out.empty?
78
- output.push(Color.template("- {bw}#{topic.title}{x}"))
78
+ output.push("- {bw}#{topic.title}{x}".c)
79
79
  output.push(s_out.join("\n"))
80
80
  end
81
81
  end
@@ -95,9 +95,9 @@ module Howzit
95
95
  default = !$stdout.isatty || Howzit.options[:default]
96
96
  # First make sure there isn't already a buildnotes file
97
97
  if note_file
98
- fname = Color.template("{by}#{note_file}{bw}")
98
+ fname = "{by}#{note_file}{bw}".c
99
99
  unless default
100
- res = Prompt.yn("#{fname} exists and appears to be a build note, continue anyway?", false)
100
+ res = Prompt.yn("#{fname} exists and appears to be a build note, continue anyway?", default: false)
101
101
  unless res
102
102
  puts 'Canceled'
103
103
  Process.exit 0
@@ -106,23 +106,25 @@ module Howzit
106
106
  end
107
107
 
108
108
  title = File.basename(Dir.pwd)
109
+ # prompt = TTY::Prompt.new
109
110
  if default
110
111
  input = title
111
112
  else
112
- printf Color.template("{bw}Project name {xg}[#{title}]{bw}: {x}")
113
+ # title = prompt.ask("{bw}Project name:{x}".c, default: title)
114
+ printf "{bw}Project name {xg}[#{title}]{bw}: {x}".c
113
115
  input = $stdin.gets.chomp
114
116
  title = input unless input.empty?
115
117
  end
116
118
  summary = ''
117
119
  unless default
118
- printf Color.template('{bw}Project summary: {x}')
120
+ printf '{bw}Project summary: {x}'.c
119
121
  input = $stdin.gets.chomp
120
122
  summary = input unless input.empty?
121
123
  end
122
124
 
123
125
  fname = 'buildnotes.md'
124
126
  unless default
125
- printf Color.template("{bw}Build notes filename (must begin with 'howzit' or 'build')\n{xg}[#{fname}]{bw}: {x}")
127
+ printf "{bw}Build notes filename (must begin with 'howzit' or 'build')\n{xg}[#{fname}]{bw}: {x}".c
126
128
  input = $stdin.gets.chomp
127
129
  fname = input unless input.empty?
128
130
  end
@@ -153,8 +155,8 @@ module Howzit
153
155
  EOBUILDNOTES
154
156
 
155
157
  if File.exist?(fname) && !default
156
- file = Color.template("{by}#{fname}")
157
- res = Prompt.yn("Are you absolutely sure you want to overwrite #{file}", false)
158
+ file = "{by}#{fname}".c
159
+ res = Prompt.yn("Are you absolutely sure you want to overwrite #{file}", default: false)
158
160
 
159
161
  unless res
160
162
  puts 'Canceled'
@@ -164,8 +166,16 @@ module Howzit
164
166
 
165
167
  File.open(fname, 'w') do |f|
166
168
  f.puts note
167
- puts Color.template("{by}Build notes for #{title} written to #{fname}")
169
+ puts "{by}Build notes for #{title} written to #{fname}".c
168
170
  end
171
+
172
+ if File.exist?(fname) && !default
173
+ res = Prompt.yn("{bg}Do you want to open {bw}#{file} {bg}for editing?{x}".c, default: false)
174
+
175
+ edit_note if res
176
+ end
177
+
178
+ Process.exit 0
169
179
  end
170
180
 
171
181
  def note_file
@@ -259,8 +269,8 @@ module Howzit
259
269
  required = t_meta['required'].strip.split(/\s*,\s*/)
260
270
  required.each do |req|
261
271
  unless @metadata.keys.include?(req.downcase)
262
- warn Color.template(%({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}))
263
- warn Color.template(%({xr}Please define {by}#{req.downcase}{xr} in build notes{x}))
272
+ warn %({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c
273
+ warn %({xr}Please define {by}#{req.downcase}{xr} in build notes{x}).c
264
274
  Process.exit 1
265
275
  end
266
276
  end
@@ -410,7 +420,7 @@ module Howzit
410
420
  raise "Invalid editor (#{editor})" unless Util.valid_command?(editor)
411
421
 
412
422
  if note_file.nil?
413
- res = Prompt.yn('No build notes file found, create one?', true)
423
+ res = Prompt.yn('No build notes file found, create one?', default: true)
414
424
 
415
425
  create_note if res
416
426
  edit_note
@@ -500,7 +510,7 @@ module Howzit
500
510
  matches = find_topic(s)
501
511
 
502
512
  if matches.empty?
503
- output.push(Color.template(%({bR}ERROR:{xr} No topic match found for {bw}#{s}{x}\n)))
513
+ output.push(%({bR}ERROR:{xr} No topic match found for {bw}#{s}{x}\n).c)
504
514
  else
505
515
  case Howzit.options[:multiple_matches]
506
516
  when :first
data/lib/howzit/hash.rb CHANGED
@@ -32,4 +32,24 @@ class ::Hash
32
32
  def deep_thaw!
33
33
  replace deep_thaw
34
34
  end
35
+
36
+ # Turn all keys into string
37
+ #
38
+ # Return a copy of the hash where all its keys are strings
39
+ def stringify_keys
40
+ each_with_object({}) { |(k, v), hsh| hsh[k.to_s] = v.is_a?(Hash) ? v.stringify_keys : v }
41
+ end
42
+
43
+ def stringify_keys!
44
+ replace stringify_keys
45
+ end
46
+
47
+ # Turn all keys into symbols
48
+ def symbolize_keys
49
+ each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
50
+ end
51
+
52
+ def symbolize_keys!
53
+ replace symbolize_keys
54
+ end
35
55
  end
data/lib/howzit/prompt.rb CHANGED
@@ -4,16 +4,18 @@ module Howzit
4
4
  # Command line prompt utils
5
5
  module Prompt
6
6
  class << self
7
- def yn(prompt, default = true)
8
- return default if !$stdout.isatty
7
+ def yn(prompt, default: true)
8
+ return default unless $stdout.isatty
9
9
 
10
- system 'stty cbreak'
11
- yn = color_single_options(default ? %w[Y n] : %w[y N])
12
- $stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
13
- res = $stdin.sysread 1
14
- res.chomp!
15
- puts
16
- system 'stty cooked'
10
+ return default if Howzit.options[:default]
11
+
12
+ system 'stty cbreak'
13
+ yn = color_single_options(default ? %w[Y n] : %w[y N])
14
+ $stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
15
+ res = $stdin.sysread 1
16
+ res.chomp!
17
+ puts
18
+ system 'stty cooked'
17
19
  res.empty? ? default : res =~ /y/i
18
20
  end
19
21
 
@@ -28,6 +28,10 @@ module Howzit
28
28
  end
29
29
  end
30
30
 
31
+ def c
32
+ Color.template(self)
33
+ end
34
+
31
35
  def to_rx
32
36
  case Howzit.options[:matching]
33
37
  when 'exact'
data/lib/howzit/task.rb CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  module Howzit
4
4
  class Task
5
- attr_reader :type, :title, :action, :parent
5
+ attr_reader :type, :title, :action, :parent, :optional, :default
6
6
 
7
- def initialize(type, title, action, parent = nil)
7
+ def initialize(type, title, action, parent = nil, optional: false, default: true)
8
8
  @type = type
9
9
  @title = title
10
10
  @action = action.render_arguments
11
11
  @parent = parent
12
+ @optional = optional
13
+ @default = default
12
14
  end
13
15
 
14
16
  def to_s
@@ -16,7 +18,7 @@ module Howzit
16
18
  end
17
19
 
18
20
  def to_list
19
- " * #{@type}: #{@title}"
21
+ " * #{@type}: #{@title.gsub(/\\n/, '\​n')}"
20
22
  end
21
23
  end
22
24
  end
data/lib/howzit/topic.rb CHANGED
@@ -28,14 +28,21 @@ module Howzit
28
28
  if @tasks.count.positive?
29
29
  unless @prereqs.empty?
30
30
  puts @prereqs.join("\n\n")
31
- res = Prompt.yn('This task has prerequisites, have they been met?', true)
31
+ res = Prompt.yn('This topic has prerequisites, have they been met?', default: true)
32
32
  Process.exit 1 unless res
33
33
 
34
34
  end
35
35
 
36
36
  @tasks.each do |task|
37
+ if task.optional
38
+ q = %({bg}#{task.type.to_s.capitalize} {xw}"{bw}#{task.title}{xw}"{x}).c
39
+ res = Prompt.yn(q, default: task.default)
40
+ next unless res
41
+
42
+ end
43
+
37
44
  if task.type == :block
38
- warn Color.template("{bg}Running block {bw}#{title}{x}") if Howzit.options[:log_level] < 2
45
+ warn "{bg}Running block {bw}#{title}{x}".c if Howzit.options[:log_level] < 2
39
46
  block = task.action
40
47
  script = Tempfile.new('howzit_script')
41
48
  begin
@@ -49,22 +56,23 @@ module Howzit
49
56
  script.unlink
50
57
  end
51
58
  else
59
+ title = Howzit.options[:show_all_code] ? task.action : task.title
52
60
  case task.type
53
61
  when :include
54
62
  matches = Howzit.buildnote.find_topic(task.action)
55
63
  raise "Topic not found: #{task.action}" if matches.empty?
56
64
 
57
- warn Color.template("{by}Running tasks from {bw}#{matches[0].title}{x}") if Howzit.options[:log_level] < 2
65
+ $stderr.puts "{by}Running tasks from {bw}#{matches[0].title}{x}".c if Howzit.options[:log_level] < 2
58
66
  output.push(matches[0].run(nested: true))
59
- warn Color.template("{by}End include: #{matches[0].tasks.count} tasks") if Howzit.options[:log_level] < 2
67
+ $stderr.puts "{by}End include: #{matches[0].tasks.count} tasks".c if Howzit.options[:log_level] < 2
60
68
  tasks += matches[0].tasks.count
61
69
  when :run
62
- warn Color.template("{bg}Running {bw}#{task.title}{x}") if Howzit.options[:log_level] < 2
70
+ $stderr.puts "{bg}Running {bw}#{title}{x}".c if Howzit.options[:log_level] < 2
63
71
  system(task.action)
64
72
  tasks += 1
65
73
  when :copy
66
- warn Color.template("{bg}Copied {bw}#{task.title}{bg} to clipboard{x}") if Howzit.options[:log_level] < 2
67
- `echo #{Shellwords.escape(task.action)}'\\c'|pbcopy`
74
+ $stderr.puts "{bg}Copied {bw}#{title}{bg} to clipboard{x}".c if Howzit.options[:log_level] < 2
75
+ os_copy(task.action)
68
76
  tasks += 1
69
77
  when :open
70
78
  os_open(task.action)
@@ -73,28 +81,52 @@ module Howzit
73
81
  end
74
82
  end
75
83
  else
76
- warn Color.template("{r}--run: No {br}@directive{xr} found in {bw}#{key}{x}")
84
+ warn "{r}--run: No {br}@directive{xr} found in {bw}#{@title}{x}".c
77
85
  end
78
- output.push(Color.template("{bm}Ran #{tasks} #{tasks == 1 ? 'task' : 'tasks'}{x}")) if Howzit.options[:log_level] < 2 && !nested
86
+ output.push("{bm}Ran #{tasks} #{tasks == 1 ? 'task' : 'tasks'}{x}".c) if Howzit.options[:log_level] < 2 && !nested
79
87
 
80
88
  puts postreqs.join("\n\n") unless postreqs.empty?
81
89
 
82
90
  output
83
91
  end
84
92
 
93
+ def os_copy(string)
94
+ os = RbConfig::CONFIG['target_os']
95
+ out = "{bg}Copying {bw}#{string}".c
96
+ case os
97
+ when /darwin.*/i
98
+ warn "#{out} (macOS){x}".c if Howzit.options[:log_level] < 2
99
+ `echo #{Shellwords.escape(string)}'\\c'|pbcopy`
100
+ when /mingw|mswin/i
101
+ warn "#{out} (Windows){x}".c if Howzit.options[:log_level] < 2
102
+ `echo #{Shellwords.escape(string)} | clip`
103
+ else
104
+ if 'xsel'.available?
105
+ warn "#{out} (Linux, xsel){x}".c if Howzit.options[:log_level] < 2
106
+ `echo #{Shellwords.escape(string)}'\\c'|xsel -i`
107
+ elsif 'xclip'.available?
108
+ warn "#{out} (Linux, xclip){x}".c if Howzit.options[:log_level] < 2
109
+ `echo #{Shellwords.escape(string)}'\\c'|xclip -i`
110
+ else
111
+ warn out if Howzit.options[:log_level] < 2
112
+ warn 'Unable to determine executable for clipboard.'
113
+ end
114
+ end
115
+ end
116
+
85
117
  def os_open(command)
86
118
  os = RbConfig::CONFIG['target_os']
87
- out = Color.template("{bg}Opening {bw}#{command}")
119
+ out = "{bg}Opening {bw}#{command}".c
88
120
  case os
89
121
  when /darwin.*/i
90
- warn Color.template("#{out} (macOS){x}") if Howzit.options[:log_level] < 2
122
+ warn "#{out} (macOS){x}".c if Howzit.options[:log_level] < 2
91
123
  `open #{Shellwords.escape(command)}`
92
124
  when /mingw|mswin/i
93
- warn Color.template("#{out} (Windows){x}") if Howzit.options[:log_level] < 2
125
+ warn "#{out} (Windows){x}".c if Howzit.options[:log_level] < 2
94
126
  `start #{Shellwords.escape(command)}`
95
127
  else
96
128
  if 'xdg-open'.available?
97
- warn Color.template("#{out} (Linux){x}") if Howzit.options[:log_level] < 2
129
+ warn "#{out} (Linux){x}".c if Howzit.options[:log_level] < 2
98
130
  `xdg-open #{Shellwords.escape(command)}`
99
131
  else
100
132
  warn out if Howzit.options[:log_level] < 2
@@ -114,50 +146,57 @@ module Howzit
114
146
  output.push('')
115
147
  end
116
148
  topic = @content.dup
117
- topic.gsub!(/(?mi)^(`{3,})run *([^\n]*)[\s\S]*?\n\1\s*$/, '@@@run \2') unless Howzit.options[:show_all_code]
149
+ topic.gsub!(/(?mi)^(`{3,})run([?!]*) *([^\n]*)[\s\S]*?\n\1\s*$/, '@@@run\2 \3') unless Howzit.options[:show_all_code]
118
150
  topic.split(/\n/).each do |l|
119
151
  case l
120
152
  when /@(before|after|prereq|end)/
121
153
  next
122
- when /@include\((.*?)\)/
123
-
124
- m = Regexp.last_match
125
- matches = Howzit.buildnote.find_topic(m[1])
154
+ when /@include(?<optional>[!?]{1,2})?\((?<action>.*?)\)/
155
+ m = Regexp.last_match.named_captures.symbolize_keys
156
+ matches = Howzit.buildnote.find_topic(m[:action])
126
157
  unless matches.empty?
127
- if opt[:single]
128
- title = "From #{matches[0].title}:"
129
- color = '{Kyd}'
130
- rule = '{kKd}'
131
- else
132
- title = "Include #{matches[0].title}"
133
- color = '{Kyd}'
134
- rule = '{kKd}'
135
- end
136
- unless Howzit.inclusions.include?(matches[0])
137
- output.push("#{'> ' * @nest_level}#{title}".format_header({ color: color, hr: '.', border: rule }))
158
+ i_topic = matches[0]
159
+ rule = '{kKd}'
160
+ color = '{Kyd}'
161
+ option = if i_topic.tasks.empty?
162
+ ''
163
+ else
164
+ optional = m[:optional] =~ /[?!]+/ ? true : false
165
+ default = m[:optional] =~ /!/ ? false : true
166
+ if optional
167
+ default ? " {xKk}[{gbK}Y{xKk}/{dbwK}n{xKk}]{x}#{color}".c : " {xKk}[{dbwK}y{xKk}/{bgK}N{xKk}]{x}#{color}".c
168
+ else
169
+ ''
170
+ end
171
+ end
172
+ title = "#{opt[:single] ? 'From' : 'Include'} #{i_topic.title}#{option}:"
173
+ options = { color: color, hr: '.', border: rule }
174
+ unless Howzit.inclusions.include?(i_topic)
175
+ output.push("#{'> ' * @nest_level}#{title}".format_header(options))
138
176
  end
139
177
 
140
- if opt[:single]
141
- if Howzit.inclusions.include?(matches[0])
142
- output.push("#{'> ' * @nest_level}#{title} included above".format_header({
143
- color: color, hr: '.', border: rule }))
144
- else
145
- @nest_level += 1
146
- output.concat(matches[0].print_out({ single: true, header: false }))
147
- @nest_level -= 1
148
- end
149
- unless Howzit.inclusions.include?(matches[0])
150
- output.push("#{'> ' * @nest_level}...".format_header({ color: color, hr: '.', border: rule }))
151
- end
178
+ if opt[:single] && Howzit.inclusions.include?(i_topic)
179
+ output.push("#{'> ' * @nest_level}#{title} included above".format_header(options))
180
+ elsif opt[:single]
181
+ @nest_level += 1
182
+ output.concat(i_topic.print_out({ single: true, header: false }))
183
+ output.push("#{'> ' * @nest_level}...".format_header(options))
184
+ @nest_level -= 1
152
185
  end
153
- Howzit.inclusions.push(matches[0])
186
+ Howzit.inclusions.push(i_topic)
154
187
  end
155
-
156
- when /@(run|copy|open|url|include)\((.*?)\)(.*?)$/
157
- m = Regexp.last_match
158
- cmd = m[1]
159
- obj = m[2]
160
- title = m[3].nil? ? obj : m[3]
188
+ when /@(?<cmd>run|copy|open|url|include)(?<optional>[?!]{1,2})?\((?<action>.*?)\) *(?<title>.*?)$/
189
+ m = Regexp.last_match.named_captures.symbolize_keys
190
+ cmd = m[:cmd]
191
+ obj = m[:action]
192
+ title = m[:title].empty? ? obj : m[:title].strip
193
+ optional = m[:optional] =~ /[?!]+/ ? true : false
194
+ default = m[:optional] =~ /!/ ? false : true
195
+ option = if optional
196
+ default ? ' {xk}[{g}Y{xk}/{dbw}n{xk}]{x}'.c : ' {xk}[{dbw}y{xk}/{g}N{xk}]{x}'.c
197
+ else
198
+ ''
199
+ end
161
200
  icon = case cmd
162
201
  when 'run'
163
202
  "\u{25B6}"
@@ -167,21 +206,35 @@ module Howzit
167
206
  "\u{279A}"
168
207
  end
169
208
 
170
- output.push(Color.template("{bmK}#{icon} {bwK}#{title.gsub(/\\n/, '\​n')}{x}"))
171
- when /(`{3,})run *(.*?)$/i
172
- m = Regexp.last_match
173
- desc = m[2].length.positive? ? "Block: #{m[2]}" : 'Code Block'
174
- output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}\n```"))
175
- when /@@@run *(.*?)$/i
176
- m = Regexp.last_match
177
- desc = m[1].length.positive? ? "Block: #{m[1]}" : 'Code Block'
178
- output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}"))
209
+ output.push("{bmK}#{icon} {bwK}#{title.gsub(/\\n/, '\​n')}{x}#{option}".c)
210
+ when /(?<fence>`{3,})run(?<optional>[!?]{1,2})? *(?<title>.*?)$/i
211
+ m = Regexp.last_match.named_captures.symbolize_keys
212
+ optional = m[:optional] =~ /[?!]+/ ? true : false
213
+ default = m[:optional] =~ /!/ ? false : true
214
+ option = if optional
215
+ default ? ' {xk}[{g}Y{xk}/{dbw}n{xk}]{x}'.c : ' {xk}[{dbw}y{xk}/{g}N{xk}]{x}'.c
216
+ else
217
+ ''
218
+ end
219
+ desc = m[:title].length.positive? ? "Block: #{m[:title]}#{option}" : "Code Block#{option}"
220
+ output.push("{bmK}\u{25B6} {bwK}#{desc}{x}\n```".c)
221
+ when /@@@run(?<optional>[!?]{1,2})? *(?<title>.*?)$/i
222
+ m = Regexp.last_match.named_captures.symbolize_keys
223
+ optional = m[:optional] =~ /[?!]+/ ? true : false
224
+ default = m[:optional] =~ /!/ ? false : true
225
+ option = if optional
226
+ default ? ' {xk}[{g}Y{xk}/{dbw}n{xk}]{x}'.c : ' {xk}[{dbw}y{xk}/{g}N{xk}]{x}'.c
227
+ else
228
+ ''
229
+ end
230
+ desc = m[:title].length.positive? ? "Block: #{m[:title]}#{option}" : "Code Block#{option}"
231
+ output.push("{bmK}\u{25B6} {bwK}#{desc}{x}".c)
179
232
  else
180
- l.wrap!(Howzit.options[:wrap]) if (Howzit.options[:wrap]).positive?
233
+ l.wrap!(Howzit.options[:wrap]) if Howzit.options[:wrap].positive?
181
234
  output.push(l)
182
235
  end
183
236
  end
184
- output.push('') # FIXME: Is this where the extra line is coming from?
237
+ output.push('')
185
238
  end
186
239
 
187
240
  private
@@ -191,18 +244,27 @@ module Howzit
191
244
  @prereqs = @content.scan(/(?<=@before\n).*?(?=\n@end)/im).map(&:strip)
192
245
  @postreqs = @content.scan(/(?<=@after\n).*?(?=\n@end)/im).map(&:strip)
193
246
 
194
- rx = /(?:@(include|run|copy|open|url)\((.*?)\) *(.*?)(?=$)|(`{3,})run(?: +([^\n]+))?(.*?)\4)/mi
195
- directives = @content.scan(rx)
247
+ rx = /(?mix)(?:
248
+ @(?<cmd>include|run|copy|open|url)(?<optional>[!?]{1,2})?\((?<action>[^)]*?)\)(?<title>[^\n]+)?
249
+ |(?<fence>`{3,})run(?<optional2>[!?]{1,2})?(?<title2>[^\n]+)?(?<block>.*?)\k<fence>
250
+ )/
251
+ matches = []
252
+ @content.scan(rx) { matches << Regexp.last_match }
253
+ matches.each do |m|
254
+ c = m.named_captures.symbolize_keys
196
255
 
197
- directives.each do |c|
198
- if c[0].nil?
199
- title = c[4] ? c[4].strip : ''
200
- block = c[5].strip
201
- runnable << Howzit::Task.new(:block, title, block)
256
+ if c[:cmd].nil?
257
+ optional = c[:optional2] =~ /[?!]{1,2}/ ? true : false
258
+ default = c[:optional2] =~ /!/ ? false : true
259
+ title = c[:title2].nil? ? '' : c[:title2].strip
260
+ block = c[:block]&.strip
261
+ runnable << Howzit::Task.new(:block, title, block, optional: optional, default: default)
202
262
  else
203
- cmd = c[0]
204
- obj = c[1]
205
- title = c[3] || obj
263
+ cmd = c[:cmd]
264
+ optional = c[:optional] =~ /[?!]{1,2}/ ? true : false
265
+ default = c[:optional] =~ /!/ ? false : true
266
+ obj = c[:action]
267
+ title = c[:title].nil? ? obj : c[:title].strip
206
268
 
207
269
  case cmd
208
270
  when /include/i
@@ -215,17 +277,15 @@ module Howzit
215
277
  # end
216
278
  # runnable.concat(tasks)
217
279
  # end
218
- title = c[3] || obj
219
- runnable << Howzit::Task.new(:include, title, obj)
280
+ runnable << Howzit::Task.new(:include, title, obj, optional: optional, default: default)
220
281
  when /run/i
221
- title = c[3] || obj
222
- # warn Color.template("{bg}Running {bw}#{obj}{x}") if Howzit.options[:log_level] < 2
223
- runnable << Howzit::Task.new(:run, title, obj)
282
+ # warn "{bg}Running {bw}#{obj}{x}".c if Howzit.options[:log_level] < 2
283
+ runnable << Howzit::Task.new(:run, title, obj, optional: optional, default: default)
224
284
  when /copy/i
225
- # warn Color.template("{bg}Copied {bw}#{obj}{bg} to clipboard{x}") if Howzit.options[:log_level] < 2
226
- runnable << Howzit::Task.new(:copy, title, Shellwords.escape(obj))
285
+ # warn "{bg}Copied {bw}#{obj}{bg} to clipboard{x}".c if Howzit.options[:log_level] < 2
286
+ runnable << Howzit::Task.new(:copy, title, Shellwords.escape(obj), optional: optional, default: default)
227
287
  when /open|url/i
228
- runnable << Howzit::Task.new(:open, title, obj)
288
+ runnable << Howzit::Task.new(:open, title, obj, optional: optional, default: default)
229
289
  end
230
290
  end
231
291
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # Primary module for this gem.
3
4
  module Howzit
4
5
  # Current Howzit version.
5
- VERSION = '2.0.2'.freeze
6
+ VERSION = '2.0.5'
6
7
  end
data/lib/howzit.rb CHANGED
@@ -20,6 +20,7 @@ require 'tempfile'
20
20
  require 'yaml'
21
21
 
22
22
  require 'tty/screen'
23
+ # require 'tty/prompt'
23
24
 
24
25
  CONFIG_DIR = '~/.config/howzit'
25
26
  CONFIG_FILE = 'howzit.yaml'
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Howzit::BuildNote do
6
+ subject(:how) { @hz }
7
+
8
+ describe ".note_file" do
9
+ it "locates a build note file" do
10
+ expect(how.note_file).not_to be_empty
11
+ expect(how.note_file).to match /builda.md$/
12
+ end
13
+ end
14
+
15
+ describe ".grep" do
16
+ it "finds topic containing 'bermuda'" do
17
+ expect(how.grep('bermuda').map { |topic| topic.title }).to include('Topic Tropic')
18
+ end
19
+ it "does not return non-matching topic" do
20
+ expect(how.grep('bermuda').map { |topic| topic.title }).not_to include('Topic Balogna')
21
+ end
22
+ end
23
+
24
+ describe ".find_topic" do
25
+ it "finds the Topic Tropic topic" do
26
+ matches = how.find_topic('tropic')
27
+ expect(matches.count).to eq 1
28
+ expect(matches[0].title).to eq 'Topic Tropic'
29
+ end
30
+ it "fuzzy matches" do
31
+ Howzit.options[:matching] = 'fuzzy'
32
+ matches = how.find_topic('trpc')
33
+ expect(matches.count).to eq 1
34
+ expect(matches[0].title).to eq 'Topic Tropic'
35
+ end
36
+ it "succeeds with partial match" do
37
+ Howzit.options[:matching] = 'partial'
38
+ matches = how.find_topic('trop')
39
+ expect(matches.count).to eq 1
40
+ expect(matches[0].title).to eq 'Topic Tropic'
41
+ end
42
+ it "succeeds with beginswith match" do
43
+ Howzit.options[:matching] = 'beginswith'
44
+ matches = how.find_topic('topic')
45
+ expect(matches.count).to eq 3
46
+ expect(matches[0].title).to eq 'Topic Balogna'
47
+ end
48
+ it "succeeds with exact match" do
49
+ Howzit.options[:matching] = 'exact'
50
+ matches = how.find_topic('topic tropic')
51
+ expect(matches.count).to eq 1
52
+ expect(matches[0].title).to eq 'Topic Tropic'
53
+ end
54
+ it "fails with incomplete exact match" do
55
+ Howzit.options[:matching] = 'exact'
56
+ matches = how.find_topic('topic trop')
57
+ expect(matches.count).to eq 0
58
+ end
59
+ end
60
+
61
+ describe ".topics" do
62
+ it "contains 4 topics" do
63
+ expect(how.list_topics.count).to eq 3
64
+ end
65
+ it "outputs a newline-separated string for completion" do
66
+ expect(how.list_completions.scan(/\n/).count).to eq 2
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,22 @@
1
+ # spec
2
+
3
+
4
+
5
+ ## File Structure
6
+
7
+ Where are the main editable files? Is there a dist/build folder that should be ignored?
8
+
9
+ ## Build
10
+
11
+ What build system/parameters does this use?
12
+
13
+ @run(./build command)
14
+
15
+ ## Deploy
16
+
17
+ What are the procedures/commands to deploy this project?
18
+
19
+ ## Other
20
+
21
+ Version control notes, additional gulp/rake/make/etc tasks...
22
+
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Howzit::BuildNote do
@@ -9,102 +11,3 @@ describe Howzit::BuildNote do
9
11
  end
10
12
  end
11
13
  end
12
-
13
- describe Howzit::Task do
14
- subject(:task) { Howzit::Task.new(:run, 'List Directory', 'ls') }
15
-
16
- describe ".new" do
17
- it "makes a new task instance" do
18
- expect(task).to be_a Howzit::Task
19
- end
20
- end
21
- end
22
-
23
- describe Howzit::Topic do
24
- title = 'Test Title'
25
- content = 'Test Content'
26
- subject(:topic) { Howzit::Topic.new(title, content) }
27
-
28
- describe ".new" do
29
- it "makes a new topic instance" do
30
- expect(topic).to be_a Howzit::Topic
31
- end
32
- it "has the correct title" do
33
- expect(topic.title).to eq title
34
- end
35
- it "has the correct content" do
36
- expect(topic.content).to eq content
37
- end
38
- end
39
- end
40
-
41
- describe Howzit::BuildNote do
42
- Dir.chdir('spec')
43
- Howzit.options[:include_upstream] = false
44
- Howzit.options[:default] = true
45
- hz = Howzit.buildnote
46
- hz.create_note
47
-
48
- subject(:how) { hz }
49
-
50
- describe ".note_file" do
51
- it "locates a build note file" do
52
- expect(how.note_file).not_to be_empty
53
- end
54
- end
55
-
56
- describe ".grep" do
57
- it "finds topic containing 'editable'" do
58
- expect(how.grep('editable').map { |topic| topic.title }).to include('File Structure')
59
- end
60
- it "does not return non-matching topic" do
61
- expect(how.grep('editable').map { |topic| topic.title }).not_to include('Build')
62
- end
63
- end
64
-
65
- describe ".find_topic" do
66
- it "finds the File Structure topic" do
67
- matches = how.find_topic('file struct')
68
- expect(matches.count).to eq 1
69
- expect(matches[0].title).to eq 'File Structure'
70
- end
71
- it "fuzzy matches" do
72
- Howzit.options[:matching] = 'fuzzy'
73
- matches = how.find_topic('flestct')
74
- expect(matches.count).to eq 1
75
- expect(matches[0].title).to eq 'File Structure'
76
- end
77
- it "succeeds with partial match" do
78
- Howzit.options[:matching] = 'partial'
79
- matches = how.find_topic('structure')
80
- expect(matches.count).to eq 1
81
- expect(matches[0].title).to eq 'File Structure'
82
- end
83
- it "succeeds with beginswith match" do
84
- Howzit.options[:matching] = 'beginswith'
85
- matches = how.find_topic('file')
86
- expect(matches.count).to eq 1
87
- expect(matches[0].title).to eq 'File Structure'
88
- end
89
- it "succeeds with exact match" do
90
- Howzit.options[:matching] = 'exact'
91
- matches = how.find_topic('file structure')
92
- expect(matches.count).to eq 1
93
- expect(matches[0].title).to eq 'File Structure'
94
- end
95
- it "fails with incomplete exact match" do
96
- Howzit.options[:matching] = 'exact'
97
- matches = how.find_topic('file struct')
98
- expect(matches.count).to eq 0
99
- end
100
- end
101
-
102
- describe ".topics" do
103
- it "contains 4 topics" do
104
- expect(how.list_topics.count).to eq 4
105
- end
106
- it "outputs a newline-separated string for completion" do
107
- expect(how.list_completions.scan(/\n/).count).to eq 3
108
- end
109
- end
110
- end
data/spec/spec_helper.rb CHANGED
@@ -13,7 +13,52 @@ require 'howzit'
13
13
 
14
14
  RSpec.configure do |c|
15
15
  c.expect_with(:rspec) { |e| e.syntax = :expect }
16
+
16
17
  c.before(:each) do
17
18
  allow(FileUtils).to receive(:remove_entry_secure).with(anything)
19
+ save_buildnote
20
+ Howzit.options[:include_upstream] = false
21
+ Howzit.options[:default] = true
22
+ @hz = Howzit.buildnote
23
+ end
24
+
25
+ c.after(:each) do
26
+ delete_buildnote
18
27
  end
19
28
  end
29
+
30
+ def save_buildnote
31
+ note = <<~EONOTE
32
+ # Howzit Test
33
+
34
+ ## Topic Balogna
35
+
36
+ @before
37
+ This should be a prerequisite.
38
+ @end
39
+
40
+ @run(ls -1 &> /dev/null) Null Output
41
+ @include(Topic Tropic)
42
+
43
+ @after
44
+ This should be a postrequisite.
45
+ @end
46
+
47
+ ## Topic Banana
48
+
49
+ This is just another topic.
50
+
51
+ - It has a list in it
52
+ - That's pretty fun, right?
53
+
54
+ ## Topic Tropic
55
+
56
+ Bermuda, Bahama, something something wanna.
57
+ @copy(Balogna) Just some balogna
58
+ EONOTE
59
+ File.open('builda.md', 'w') { |f| f.puts note }
60
+ end
61
+
62
+ def delete_buildnote
63
+ FileUtils.rm('builda.md')
64
+ end
data/spec/task_spec.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Howzit::Task do
6
+ subject(:task) { Howzit::Task.new(:run, 'List Directory', 'ls') }
7
+
8
+ describe ".new" do
9
+ it "makes a new task instance" do
10
+ expect(task).to be_a Howzit::Task
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Howzit::Topic do
6
+ title = 'Test Title'
7
+ content = 'Test Content'
8
+ subject(:topic) { Howzit::Topic.new(title, content) }
9
+
10
+ describe ".new" do
11
+ it "makes a new topic instance" do
12
+ expect(topic).to be_a Howzit::Topic
13
+ end
14
+ it "has the correct title" do
15
+ expect(topic.title).to eq title
16
+ end
17
+ it "has the correct content" do
18
+ expect(topic.content).to eq content
19
+ end
20
+ end
21
+ end
22
+
23
+ describe Howzit::Topic do
24
+ subject(:topic) { @hz.find_topic('Topic Balogna')[0] }
25
+
26
+ describe ".title" do
27
+ it "has the correct title" do
28
+ expect(topic.title).to match /Topic Balogna/
29
+ end
30
+ end
31
+
32
+ describe ".tasks" do
33
+ it "has 2 tasks" do
34
+ expect(topic.tasks.count).to eq 2
35
+ end
36
+ end
37
+
38
+ describe ".prereqs" do
39
+ it "has prereq" do
40
+ expect(topic.prereqs.count).to eq 1
41
+ end
42
+ it "has postreq" do
43
+ expect(topic.postreqs.count).to eq 1
44
+ end
45
+ end
46
+
47
+ describe ".run" do
48
+ Howzit.options[:default] = true
49
+ it "shows prereq and postreq" do
50
+ expect { topic.run }.to output(/prerequisite/).to_stdout
51
+ expect { topic.run }.to output(/postrequisite/).to_stdout
52
+ end
53
+ it "Copies to clipboard" do
54
+ expect {
55
+ ENV['RUBYOPT'] = '-W1'
56
+ Howzit.options[:log_level] = 0
57
+ topic.run
58
+ }.to output(/Copied/).to_stderr
59
+ end
60
+ end
61
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: howzit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-03 00:00:00.000000000 Z
11
+ date: 2022-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -280,8 +280,12 @@ files:
280
280
  - lib/howzit/util.rb
281
281
  - lib/howzit/version.rb
282
282
  - spec/.rubocop.yml
283
+ - spec/buildnote_spec.rb
284
+ - spec/buildnotes.md.bak
283
285
  - spec/ruby_gem_spec.rb
284
286
  - spec/spec_helper.rb
287
+ - spec/task_spec.rb
288
+ - spec/topic_spec.rb
285
289
  - update_readmes.rb
286
290
  homepage: https://github.com/ttscoff/howzit
287
291
  licenses:
@@ -309,5 +313,9 @@ summary: Provides a way to access Markdown project notes by topic with query cap
309
313
  and the ability to execute the tasks it describes.
310
314
  test_files:
311
315
  - spec/.rubocop.yml
316
+ - spec/buildnote_spec.rb
317
+ - spec/buildnotes.md.bak
312
318
  - spec/ruby_gem_spec.rb
313
319
  - spec/spec_helper.rb
320
+ - spec/task_spec.rb
321
+ - spec/topic_spec.rb