bashly 1.0.7 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/lib/bashly/cli.rb +1 -0
  4. data/lib/bashly/commands/completions.rb +1 -1
  5. data/lib/bashly/commands/render.rb +103 -0
  6. data/lib/bashly/concerns/validation_helpers.rb +1 -1
  7. data/lib/bashly/config.rb +0 -2
  8. data/lib/bashly/extensions/string.rb +17 -1
  9. data/lib/bashly/extensions/yaml.rb +4 -1
  10. data/lib/bashly/libraries/config/config.sh +74 -94
  11. data/lib/bashly/libraries/ini/ini.sh +110 -0
  12. data/lib/bashly/libraries/libraries.yml +40 -1
  13. data/lib/bashly/libraries/render/mandoc/README.md +45 -0
  14. data/lib/bashly/libraries/render/mandoc/mandoc.gtx +196 -0
  15. data/lib/bashly/libraries/render/mandoc/render.rb +34 -0
  16. data/lib/bashly/libraries/render/mandoc/summary.txt +1 -0
  17. data/lib/bashly/libraries/render/markdown/README.md +42 -0
  18. data/lib/bashly/libraries/render/markdown/markdown.gtx +186 -0
  19. data/lib/bashly/libraries/render/markdown/render.rb +23 -0
  20. data/lib/bashly/libraries/render/markdown/summary.txt +1 -0
  21. data/lib/bashly/refinements/compose_refinements.rb +0 -2
  22. data/lib/bashly/render_context.rb +30 -0
  23. data/lib/bashly/render_source.rb +71 -0
  24. data/lib/bashly/script/base.rb +2 -1
  25. data/lib/bashly/script/command.rb +17 -0
  26. data/lib/bashly/version.rb +1 -1
  27. data/lib/bashly/views/command/dependencies_filter.gtx +1 -1
  28. data/lib/bashly/views/command/footer.gtx +1 -1
  29. data/lib/bashly/views/command/parse_requirements_while.gtx +1 -1
  30. data/lib/bashly/views/command/usage.gtx +2 -2
  31. data/lib/bashly/views/command/usage_commands.gtx +2 -2
  32. data/lib/bashly.rb +56 -11
  33. metadata +41 -9
@@ -0,0 +1,196 @@
1
+ # Reference: https://linux.die.net/man/5/pandoc_markdown
2
+
3
+ if version
4
+ > % {{ full_name.to_hyphen }}(1) Version {{ version }} | {{ summary }}
5
+ else
6
+ > % {{ full_name.to_hyphen }}(1) | {{ summary }}
7
+ end
8
+
9
+ > % {{ x_mandoc_authors&.for_manpage }}
10
+ > % {{ Date.today.strftime "%B %Y" }}
11
+ >
12
+
13
+ > NAME
14
+ > ==================================================
15
+ >
16
+ > **{{ full_name }}** - {{ summary }}
17
+ >
18
+
19
+ > SYNOPSIS
20
+ > ==================================================
21
+ >
22
+ > {{ usage_string.gsub(/^#{full_name}/, "**#{full_name}**") }}
23
+ >
24
+
25
+ > DESCRIPTION
26
+ > ==================================================
27
+ >
28
+ > {{ help.for_manpage }}
29
+ >
30
+
31
+ if default
32
+ > - *Default Command*
33
+ end
34
+ if alt.any?
35
+ > - Alias: **{{ alt.join ', ' }}**
36
+ end
37
+ if extensible
38
+ if extensible.is_a? String
39
+ > - Extensible: **{{ extensible }}**
40
+ else
41
+ > - *Extensible*
42
+ end
43
+ end
44
+ >
45
+
46
+ if public_commands.any?
47
+ grouped_commands.each do |group, commands|
48
+ > {{ group.gsub(/:$/, '').upcase }}
49
+ > ==================================================
50
+ >
51
+ commands.each do |subcommand|
52
+ > {{ subcommand.full_name }}
53
+ > --------------------------------------------------
54
+ >
55
+ > {{ subcommand.summary.for_manpage }}
56
+ >
57
+ end
58
+ >
59
+ end
60
+ end
61
+
62
+ if args.any?
63
+ > ARGUMENTS
64
+ > ==================================================
65
+ >
66
+ args.each do |arg|
67
+ > {{ arg.name.upcase }}
68
+ > --------------------------------------------------
69
+ >
70
+ > {{ arg.help.for_manpage }}
71
+ >
72
+ if arg.required
73
+ > - *Required*
74
+ end
75
+ if arg.repeatable
76
+ > - *Repeatable*
77
+ end
78
+ if arg.default
79
+ > - Default Value: **{{ arg.default }}**
80
+ end
81
+ if arg.allowed
82
+ > - Allowed Values: **{{ arg.allowed.join(', ') }}**
83
+ end
84
+ >
85
+ end
86
+
87
+ if catch_all.label && catch_all.help
88
+ > {{ catch_all.label }}
89
+ > --------------------------------------------------
90
+ >
91
+ if catch_all.help
92
+ > {{ catch_all.help.for_manpage }}
93
+ >
94
+ end
95
+ >
96
+
97
+ if catch_all.required?
98
+ > - *Required*
99
+ >
100
+ end
101
+ end
102
+ end
103
+
104
+ if flags.any?
105
+ > OPTIONS
106
+ > ==================================================
107
+ >
108
+ flags.each do |flag|
109
+ > {{ flag.usage_string }}
110
+ > --------------------------------------------------
111
+ >
112
+ > {{ flag.help.for_manpage }}
113
+ >
114
+
115
+ if flag.required
116
+ > - *Required*
117
+ end
118
+ if flag.repeatable
119
+ > - *Repeatable*
120
+ end
121
+ if flag.default
122
+ > - Default Value: **{{ flag.default }}**
123
+ end
124
+ if flag.allowed
125
+ > - Allowed Values: **{{ flag.allowed.join(', ') }}**
126
+ end
127
+ if flag.conflicts
128
+ > - Conflicts With: **{{ flag.conflicts.join(', ') }}**
129
+ end
130
+ >
131
+ end
132
+ end
133
+
134
+ if dependencies.any?
135
+ > DEPENDENCIES
136
+ > ==================================================
137
+ >
138
+ dependencies.each do |dependency|
139
+ > {{ dependency.commands.join ', ' }}
140
+ > --------------------------------------------------
141
+ >
142
+ if dependency.help
143
+ > {{ dependency.help.for_manpage }}
144
+ >
145
+ end
146
+ >
147
+ end
148
+ end
149
+
150
+ if public_environment_variables.any?
151
+ > ENVIRONMENT VARIABLES
152
+ > ==================================================
153
+ >
154
+ public_environment_variables.each do |environment_variable|
155
+ > {{ environment_variable.name.upcase }}
156
+ > --------------------------------------------------
157
+ >
158
+ > {{ environment_variable.help.for_manpage }}
159
+ >
160
+
161
+ if environment_variable.required
162
+ > - *Required*
163
+ end
164
+ if environment_variable.default
165
+ > - Default Value: **{{ environment_variable.default }}**
166
+ end
167
+ >
168
+ end
169
+ end
170
+
171
+ if examples
172
+ > EXAMPLES
173
+ > ==================================================
174
+ >
175
+ > ~~~
176
+ examples.each do |example|
177
+ > {{ example.for_manpage }}
178
+ >
179
+ end
180
+ > ~~~
181
+ >
182
+ end
183
+
184
+ if public_commands.any? || parents.any?
185
+ > SEE ALSO
186
+ > ==================================================
187
+ >
188
+ if parents.any?
189
+ > **{{ parents.first }}**(1)
190
+ end
191
+ = public_commands.map { |x| "**#{x.full_name.to_hyphen}**(1)" }.join ', '
192
+ >
193
+ end
194
+
195
+ = x_mandoc_footer&.for_manpage
196
+ >
@@ -0,0 +1,34 @@
1
+ # render script - mandoc
2
+ require 'gtx'
3
+
4
+ # Load the GTX template
5
+ template = "#{source}/mandoc.gtx"
6
+ gtx = GTX.load_file template
7
+
8
+ # Define a reusable code block for rendering a man page for a command
9
+ save_manpage = lambda { |command|
10
+ mdfile = "#{target}/#{command.full_name.tr(' ', '-')}.md"
11
+ manfile = "#{target}/#{command.full_name.tr(' ', '-')}.1"
12
+ save mdfile, gtx.parse(command)
13
+
14
+ # The pandoc command that creates a manpage from markdown
15
+ cmd = %[pandoc -f markdown-smart -s --to man "#{mdfile}" > "#{manfile}"]
16
+ success = system cmd
17
+ raise "Failed running pandoc\nMake sure the following command succeeds and try again:\n\n #{cmd}" unless success
18
+
19
+ say "g`saved` #{manfile}"
20
+ }
21
+
22
+ # Render the main command
23
+ save_manpage.call command
24
+
25
+ # Render all subcommands
26
+ command.deep_commands.reject(&:private).each do |subcommand|
27
+ save_manpage.call subcommand
28
+ end
29
+
30
+ # Show one of the files if requested
31
+ if show
32
+ file = "#{target}/#{show}"
33
+ system "man #{file}" if File.exist?(file)
34
+ end
@@ -0,0 +1 @@
1
+ Render man pages for your script
@@ -0,0 +1,42 @@
1
+ # Render markdown
2
+
3
+ Render markdown documents for your script.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ # Generate all documents to the ./docs directory
9
+ $ bashly render :markdown docs
10
+
11
+ # Generate on change, and show one of the files
12
+ $ bashly render :markdown docs --watch --show index.md
13
+ ```
14
+
15
+ ## Supported custom definitions
16
+
17
+ Add these definitions to your `bashly.yml` to render them in your
18
+ markdown:
19
+
20
+ ### Footer: `x_markdown_footer`
21
+
22
+ Add additional sections to your man pages. This field is expected
23
+ to be in markdown format.
24
+
25
+ #### Example
26
+
27
+ ```yaml
28
+ x_markdown_footer: |-
29
+ # ISSUE TRACKER
30
+
31
+ Report issues at <https://github.com/lanalang/smallville>
32
+ ```
33
+
34
+ ## Markdown server
35
+
36
+ In order to view your markdown files in a browser, you can use the
37
+ [Madness markdown server](https://madness.dannyb.co/):
38
+
39
+ ```bash
40
+ $ gem install madness
41
+ $ madness server docs
42
+ ```
@@ -0,0 +1,186 @@
1
+ # === Header
2
+
3
+ > # {{ full_name }}
4
+ >
5
+ > {{ help.for_markdown }}
6
+ >
7
+
8
+ attributes = version || alt.any? || default || extensible
9
+
10
+ if attributes
11
+ > | Attributes | &nbsp;
12
+ > |------------------|-------------
13
+ if version
14
+ > | Version: | {{ version }}
15
+ end
16
+ if alt.any?
17
+ > | Alias: | {{ alt.join ', ' }}
18
+ end
19
+ if default
20
+ > | Default Command: | ✓ Yes
21
+ end
22
+ if extensible
23
+ > | Extensible: | {{ extensible.is_a?(String) ? extensible : "✓ Yes" }}
24
+ end
25
+ >
26
+ end
27
+
28
+ # === Usage
29
+
30
+ > ## Usage
31
+ >
32
+ > ```bash
33
+ > {{ usage_string.for_markdown }}
34
+ > ```
35
+ >
36
+
37
+ # === Examples
38
+
39
+ if examples
40
+ > ## Examples
41
+ >
42
+ examples.each do |example|
43
+ > ```bash
44
+ > {{ example }}
45
+ > ```
46
+ >
47
+ end
48
+ end
49
+
50
+ # === Dependencies
51
+
52
+ if dependencies.any?
53
+ > ## Dependencies
54
+ >
55
+ dependencies.each do |dependency|
56
+ > #### *{{ dependency.commands.join ', ' }}*
57
+ >
58
+ > {{ dependency.help&.for_markdown }}
59
+ >
60
+ end
61
+ end
62
+
63
+ # === Environment Variables
64
+
65
+ if public_environment_variables.any?
66
+ > ## Environment Variables
67
+ >
68
+ public_environment_variables.each do |environment_variable|
69
+ attributes = environment_variable.required || environment_variable.default
70
+
71
+ > #### *{{ environment_variable.name.upcase }}*
72
+ >
73
+ > {{ environment_variable.help.for_markdown }}
74
+ >
75
+
76
+ if attributes
77
+ > | Attributes | &nbsp;
78
+ > |-----------------|-------------
79
+ if environment_variable.required
80
+ > | Required: | ✓ Yes
81
+ end
82
+ if environment_variable.default
83
+ > | Default Value: | {{ environment_variable.default }}
84
+ end
85
+ >
86
+ end
87
+ end
88
+ end
89
+
90
+ # === Commands
91
+
92
+ if commands.any?
93
+ grouped_commands.each do |group, commands|
94
+ > ## {{ group.gsub(/:$/, '') }}
95
+ >
96
+ commands.each do |subcommand|
97
+ > - [{{ subcommand.name }}]({{ subcommand.full_name.gsub(' ', '%20') }}) - {{ subcommand.summary.for_markdown }}
98
+ end
99
+ >
100
+ end
101
+ end
102
+
103
+ # === Arguments
104
+
105
+ if args.any?
106
+ > ## Arguments
107
+ >
108
+ args.each do |arg|
109
+ attributes = arg.required || arg.repeatable || arg.default || arg.allowed
110
+
111
+ > #### *{{ arg.name.upcase }}*
112
+ >
113
+ > {{ arg.help.for_markdown }}
114
+ >
115
+
116
+ if attributes
117
+ > | Attributes | &nbsp;
118
+ > |-----------------|-------------
119
+ if arg.required
120
+ > | Required: | ✓ Yes
121
+ end
122
+ if arg.repeatable
123
+ > | Repeatable: | ✓ Yes
124
+ end
125
+ if arg.default
126
+ > | Default Value: | {{ arg.default }}
127
+ end
128
+ if arg.allowed
129
+ > | Allowed Values: | {{ arg.allowed.join(', ') }}
130
+ end
131
+ >
132
+ end
133
+ end
134
+
135
+ if catch_all.label && catch_all.help
136
+ > #### *{{ catch_all.label }}*
137
+ >
138
+ > {{ catch_all.help&.for_markdown }}
139
+ >
140
+ if catch_all.required?
141
+ > | Attributes | &nbsp;
142
+ > |------------|-------------
143
+ > | Required: | ✓ Yes
144
+ >
145
+ end
146
+ end
147
+ end
148
+
149
+ # === Flags
150
+
151
+ if flags.any?
152
+ > ## Options
153
+ >
154
+ flags.each do |flag|
155
+ attributes = flag.required || flag.repeatable || flag.default || flag.allowed || flag.conflicts
156
+
157
+ > #### *{{ flag.usage_string }}*
158
+ >
159
+ > {{ flag.help.for_markdown }}
160
+ >
161
+
162
+ if attributes
163
+ > | Attributes | &nbsp;
164
+ > |-----------------|-------------
165
+ if flag.required
166
+ > | Required: | ✓ Yes
167
+ end
168
+ if flag.repeatable
169
+ > | Repeatable: | ✓ Yes
170
+ end
171
+ if flag.default
172
+ > | Default Value: | {{ flag.default }}
173
+ end
174
+ if flag.allowed
175
+ > | Allowed Values: | {{ flag.allowed.join(', ') }}
176
+ end
177
+ if flag.conflicts
178
+ > | Conflicts With: | *{{ flag.conflicts.join(', ') }}*
179
+ end
180
+ >
181
+ end
182
+ end
183
+ end
184
+
185
+ = x_markdown_footer&.for_manpage
186
+ >
@@ -0,0 +1,23 @@
1
+ # render script - markdown
2
+ require 'gtx'
3
+
4
+ # for previewing only (not needed for rendering)
5
+ require 'tty-markdown'
6
+
7
+ # Load the GTX template
8
+ template = "#{source}/markdown.gtx"
9
+ gtx = GTX.load_file template
10
+
11
+ # Render the file for the main command
12
+ save "#{target}/index.md", gtx.parse(command)
13
+
14
+ # Render a file for each subcommand
15
+ command.deep_commands.reject(&:private).each do |subcommand|
16
+ save "#{target}/#{subcommand.full_name}.md", gtx.parse(subcommand)
17
+ end
18
+
19
+ # Show one of the files if requested
20
+ if show
21
+ file = "#{target}/#{show}"
22
+ puts TTY::Markdown.parse_file(file) if File.exist?(file)
23
+ end
@@ -0,0 +1 @@
1
+ Render markdown documents for your script
@@ -1,5 +1,3 @@
1
- require 'yaml'
2
-
3
1
  module ComposeRefinements
4
2
  refine Hash do
5
3
  def compose(keyword = 'import')
@@ -0,0 +1,30 @@
1
+ require 'date' # for use by template render scripts
2
+ require 'colsole'
3
+
4
+ module Bashly
5
+ class RenderContext
6
+ include Colsole
7
+
8
+ attr_reader :source, :target, :show
9
+ attr_writer :config
10
+
11
+ def initialize(source:, target:, show: nil)
12
+ @source = source
13
+ @target = target
14
+ @show = show
15
+ end
16
+
17
+ def config
18
+ @config ||= Config.new Settings.config_path
19
+ end
20
+
21
+ def command
22
+ @command ||= Script::Command.new config
23
+ end
24
+
25
+ def save(filename, content)
26
+ File.deep_write filename, content
27
+ say "g`saved` #{filename}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ module Bashly
2
+ class RenderSource
3
+ attr_reader :selector
4
+
5
+ class << self
6
+ include AssetHelper
7
+
8
+ def internal
9
+ @internal ||= internal_dirs.to_h do |dir|
10
+ selector = File.basename(dir).to_sym
11
+ [selector, new(selector)]
12
+ end
13
+ end
14
+
15
+ def internal_dirs
16
+ @internal_dirs ||= Dir["#{internal_root}/*"].select { |x| File.directory? x }
17
+ end
18
+
19
+ def internal_root
20
+ asset('libraries/render')
21
+ end
22
+ end
23
+
24
+ def initialize(selector)
25
+ @selector = selector
26
+ end
27
+
28
+ def render(target, show: nil)
29
+ context = RenderContext.new source: path, target: target, show: show
30
+ context.instance_eval render_script
31
+ end
32
+
33
+ def internal?
34
+ selector.is_a? Symbol
35
+ end
36
+
37
+ def path
38
+ internal? ? "#{internal_root}/#{selector}" : selector
39
+ end
40
+
41
+ def exist?
42
+ Dir.exist? path
43
+ end
44
+
45
+ def summary
46
+ File.readlines(summary_file)[0].chomp
47
+ end
48
+
49
+ def readme
50
+ File.read readme_file if File.exist? readme_file
51
+ end
52
+
53
+ private
54
+
55
+ def render_script
56
+ @render_script ||= File.read "#{path}/render.rb"
57
+ end
58
+
59
+ def internal_root
60
+ self.class.internal_root
61
+ end
62
+
63
+ def summary_file
64
+ "#{path}/summary.txt"
65
+ end
66
+
67
+ def readme_file
68
+ "#{path}/README.md"
69
+ end
70
+ end
71
+ end
@@ -35,7 +35,8 @@ module Bashly
35
35
  end
36
36
 
37
37
  def respond_to_missing?(method_name, include_private = false)
38
- self.class.option_keys.include?(method_name) || super
38
+ self.class.option_keys.include?(method_name) ||
39
+ method_name.to_s.start_with?('x_') || super
39
40
  end
40
41
  end
41
42
  end
@@ -200,6 +200,23 @@ module Bashly
200
200
  end
201
201
  end
202
202
 
203
+ # Returns subcommands by group
204
+ def grouped_commands
205
+ result = {}
206
+
207
+ public_commands.each do |command|
208
+ result[command.group_string] ||= []
209
+ result[command.group_string] << command
210
+ next unless command.expose
211
+
212
+ command.public_commands.each do |subcommand|
213
+ result[command.group_string] << subcommand
214
+ end
215
+ end
216
+
217
+ result
218
+ end
219
+
203
220
  # Returns a mode identifier
204
221
  def mode
205
222
  @mode ||= if global_flags? then :global_flags
@@ -1,3 +1,3 @@
1
1
  module Bashly
2
- VERSION = '1.0.7'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -7,7 +7,7 @@ if dependencies.any?
7
7
  > else
8
8
  > printf "{{ strings[:missing_dependency] % { dependency: dependency.name } }}\n" >&2
9
9
  if dependency.help
10
- > printf "%s\n" "{{ dependency.help }}" >&2
10
+ > printf "%s\n" "{{ dependency.help.sanitize_for_print }}" >&2
11
11
  end
12
12
  > exit 1
13
13
  > fi
@@ -1,5 +1,5 @@
1
1
  = view_marker
2
2
 
3
- > printf "{{ footer.gsub("\n", '\n').gsub('"', '\"') }}\n"
3
+ > printf "{{ footer.sanitize_for_print }}\n"
4
4
  > echo
5
5
  >
@@ -27,7 +27,7 @@ if catch_all.enabled?
27
27
  > ;;
28
28
 
29
29
  else
30
- > printf "<%= strings[:invalid_flag] %>\n" "$key" >&2
30
+ > printf "{{ strings[:invalid_flag] }}\n" "$key" >&2
31
31
  > exit 1
32
32
  > ;;
33
33
 
@@ -4,7 +4,7 @@
4
4
  > if [[ -n $long_usage ]]; then
5
5
 
6
6
  if summary == help
7
- > printf "{{ caption_string }}\n"
7
+ > printf "{{ caption_string.sanitize_for_print }}\n"
8
8
  > echo
9
9
  else
10
10
  > printf "{{ full_name }}\n"
@@ -14,7 +14,7 @@ else
14
14
  end
15
15
 
16
16
  > else
17
- > printf "{{ caption_string }}\n"
17
+ > printf "{{ caption_string.sanitize_for_print }}\n"
18
18
  > echo
19
19
  > fi
20
20
  >
@@ -7,9 +7,9 @@ command_help_data.each do |group, commands|
7
7
 
8
8
  commands.each do |command, info|
9
9
  if info[:help_only]
10
- > [[ -n $long_usage ]] && printf " %s {{ info[:summary] }}\n" "{{ command.ljust(maxlen).color(:command) }}"
10
+ > [[ -n $long_usage ]] && printf " %s {{ info[:summary].sanitize_for_print }}\n" "{{ command.ljust(maxlen).color(:command) }}"
11
11
  else
12
- > printf " %s {{ info[:summary] }}\n" "{{ command.ljust(maxlen).color(:command) }}"
12
+ > printf " %s {{ info[:summary].sanitize_for_print }}\n" "{{ command.ljust(maxlen).color(:command) }}"
13
13
  end
14
14
  end
15
15