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 +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +18 -0
- data/bin/howzit +1 -1
- data/howzit.gemspec +1 -0
- data/lib/howzit/buildnote.rb +26 -16
- data/lib/howzit/hash.rb +20 -0
- data/lib/howzit/prompt.rb +11 -9
- data/lib/howzit/stringutils.rb +4 -0
- data/lib/howzit/task.rb +5 -3
- data/lib/howzit/topic.rb +137 -77
- data/lib/howzit/version.rb +2 -1
- data/lib/howzit.rb +1 -0
- data/spec/buildnote_spec.rb +69 -0
- data/spec/buildnotes.md.bak +22 -0
- data/spec/ruby_gem_spec.rb +2 -99
- data/spec/spec_helper.rb +45 -0
- data/spec/task_spec.rb +13 -0
- data/spec/topic_spec.rb +61 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95a5f6c56ebff1fb944ca8ea23cba45ca5b71647654a315fd2cf7e486d522309
|
4
|
+
data.tar.gz: f9ac61f6fad2aff412d4302d0c586e9b287f284659255215a9565c8632bdfc8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
data/lib/howzit/buildnote.rb
CHANGED
@@ -41,9 +41,9 @@ module Howzit
|
|
41
41
|
# Output a list of topic titles
|
42
42
|
def list
|
43
43
|
output = []
|
44
|
-
output.push(
|
44
|
+
output.push("{bg}Topics:{x}\n".c)
|
45
45
|
@topics.each do |topic|
|
46
|
-
output.push(
|
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(
|
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(
|
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 =
|
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
|
-
|
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
|
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
|
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 =
|
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
|
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
|
263
|
-
warn
|
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(
|
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
|
8
|
-
return default
|
7
|
+
def yn(prompt, default: true)
|
8
|
+
return default unless $stdout.isatty
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
|
data/lib/howzit/stringutils.rb
CHANGED
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
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
|
84
|
+
warn "{r}--run: No {br}@directive{xr} found in {bw}#{@title}{x}".c
|
77
85
|
end
|
78
|
-
output.push(
|
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 =
|
119
|
+
out = "{bg}Opening {bw}#{command}".c
|
88
120
|
case os
|
89
121
|
when /darwin.*/i
|
90
|
-
warn
|
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
|
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
|
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 \
|
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
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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(
|
186
|
+
Howzit.inclusions.push(i_topic)
|
154
187
|
end
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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(
|
171
|
-
when /(
|
172
|
-
m = Regexp.last_match
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
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('')
|
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 = /(
|
195
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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[
|
204
|
-
|
205
|
-
|
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
|
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
|
-
|
222
|
-
|
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
|
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
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
@@ -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
|
+
|
data/spec/ruby_gem_spec.rb
CHANGED
@@ -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
|
data/spec/topic_spec.rb
ADDED
@@ -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.
|
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-
|
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
|