markdown_exec 1.3.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -30,6 +30,7 @@ _mde() {
30
30
  if [[ ${prev} == -* ]] ; then
31
31
  case $prev in
32
32
  <% svhs.each do |svh|
33
+
33
34
  svn = svh[:long_name]
34
35
  if svn && svh[:arg_name]
35
36
  svn = '--' + svh[:long_name]
@@ -43,6 +44,21 @@ _mde() {
43
44
  <%= svn + ') COMPREPLY="' + svh[:compreply] + '"; return 0 ;;' %>
44
45
  <% end
45
46
  end
47
+
48
+ svn = svh[:short_name]
49
+ if svn && svh[:arg_name]
50
+ svn = '-' + svh[:short_name]
51
+ if svh[:compreply] == false
52
+ # nothing
53
+ elsif svh[:compreply].nil? %>
54
+ <%= svn + ') __filedirs_all; return 0 ;;' %>
55
+ <% elsif svh[:compreply].empty?
56
+ # nothing
57
+ else %>
58
+ <%= svn + ') COMPREPLY="' + svh[:compreply] + '"; return 0 ;;' %>
59
+ <% end
60
+ end
61
+
46
62
  end %>
47
63
  esac
48
64
  fi
@@ -65,11 +81,18 @@ _mde() {
65
81
  if [[ -z ${cur} ]] ; then
66
82
  case $prev in
67
83
  <% svhs.each do |svh|
84
+
68
85
  svn = svh[:long_name]
69
86
  if svn && svh[:arg_name]
70
87
  svn = '--' + svh[:long_name] %>
71
88
  <%= svn + ') COMPREPLY=".' + svh[:arg_name] + '."; return 0 ;;' %>
72
89
  <% end
90
+
91
+ svn = svh[:short_name]
92
+ if svn && svh[:arg_name]
93
+ svn = '-' + svh[:short_name] %>
94
+ <%= svn + ') COMPREPLY=".' + svh[:arg_name] + '."; return 0 ;;' %>
95
+ <% end
73
96
  end %>
74
97
  esac
75
98
  fi
data/lib/cli.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # encoding=utf-8
4
+
5
+ # utility functions to provide CLI
6
+ #
7
+ module CLI
8
+ # skip :reek:UtilityFunction
9
+ def value_for_cli(value)
10
+ case value.class.to_s
11
+ when 'String'
12
+ Shellwords.escape value
13
+ when 'FalseClass', 'TrueClass'
14
+ value ? '1' : '0'
15
+ else
16
+ Shellwords.escape value.to_s
17
+ end
18
+ end
19
+ end
data/lib/env.rb CHANGED
@@ -10,17 +10,21 @@ module Env
10
10
  # :reek:NilCheck
11
11
  # :reek:UtilityFunction
12
12
  def env_bool(name, default: false)
13
- return default if name.nil? || (val = ENV[name]).nil?
13
+ return default if name.nil? || (val = ENV.fetch(name, nil)).nil?
14
14
  return false if val.empty? || val == '0'
15
15
 
16
16
  true
17
17
  end
18
18
 
19
+ def env_bool_false(name)
20
+ !(val = (name && ENV.fetch(name, nil))).nil? && !(val.empty? || val == '0')
21
+ end
22
+
19
23
  # skip :reek:DataClump
20
24
  # skip :reek:NilCheck
21
25
  # skip :reek:UtilityFunction
22
26
  def env_int(name, default: 0)
23
- return default if name.nil? || (val = ENV[name]).nil?
27
+ return default if name.nil? || (val = ENV.fetch(name, nil)).nil?
24
28
  return default if val.empty?
25
29
 
26
30
  val.to_i
@@ -30,7 +34,7 @@ module Env
30
34
  # skip :reek:NilCheck
31
35
  # skip :reek:UtilityFunction
32
36
  def env_str(name, default: '')
33
- return default if name.nil? || (val = ENV[name]).nil?
37
+ return default if name.nil? || (val = ENV.fetch(name, nil)).nil?
34
38
 
35
39
  val || default
36
40
  end
data/lib/env_opts.rb ADDED
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ # encoding=utf-8
4
+
5
+ require_relative 'tap'
6
+
7
+ include Tap #; tap_config
8
+
9
+ # define options with initial values
10
+ # option to read value from environmnt variables
11
+ # option to cast input values
12
+ # value priority: default < environment < argument
13
+ #
14
+ class EnvOpts
15
+ attr_reader :opts, :values
16
+
17
+ def initialize(opts_raw = {}, argv = ARGV)
18
+ @opts = {}
19
+ @values = {}
20
+ add_options(opts_raw)
21
+ # parse(argv, &block) if block_given?
22
+ block_given? ? parse(argv, &block) : parse(argv)
23
+
24
+ self # rubocop:disable Lint/Void
25
+ end
26
+
27
+ # add options to menu
28
+ # calculate help text
29
+ #
30
+ def add_options(opts_raw)
31
+ return self if opts_raw.nil?
32
+
33
+ rows = opts_raw.map do |key, opt_raw|
34
+ key2 = key_name_to_option_name(key)
35
+
36
+ # set_per_options(key2, opt_raw)
37
+ @opts[key2] = (opt_raw ||= {})
38
+ set_key_value_as_cast key2, EnvOpts.optdefault(opt_raw)
39
+
40
+ set_key_value_per_environment_as_cast(key2, opt_raw)
41
+
42
+ [
43
+ [20, '-', "--#{key}"],
44
+ [16, '-', @opts[key2][:env].present? ? option_name_to_environment_name(key2, @opts[key2]) : ''],
45
+ # [24, '-', get_environment_value_from_option(key2, @opts[key2])],
46
+ [24, '-', @opts[key2][:default]],
47
+ [6, '-', if (fixed = opt_raw.fetch(:fixed, nil)).nil?
48
+ ":#{option_cast(@opts[key2])}"
49
+ else
50
+ fixed.to_s
51
+ end]
52
+ ]
53
+ end.tap_yaml 'rows'
54
+
55
+ max_widths = rows.reduce([0, 0, 0, 0]) do |memo, (c0, c1, c2, c3)|
56
+ [
57
+ [memo[0], c0[2].to_s.length].max,
58
+ [memo[1], c1[2].to_s.length].max,
59
+ [memo[2], c2[2].to_s.length].max,
60
+ [memo[3], c3[2].to_s.length].max
61
+ ]
62
+ end.tap_inspect 'max_widths'
63
+
64
+ @values['help'] = rows.map do |(c0, c1, c2, c3)|
65
+ [format("%#{c0[1]}#{max_widths[0]}s", c0[2]),
66
+ format("%#{c1[1]}#{max_widths[1]}s", c1[2]),
67
+ format("%#{c2[1]}#{max_widths[2]}s", c2[2]),
68
+ format("%#{c3[1]}#{max_widths[3]}s", c3[2])]
69
+ end.map do |row|
70
+ row.join(' ')
71
+ end.join("\n")
72
+ @opts.tap_inspect '@opts'
73
+ @values.tap_inspect '@values'
74
+
75
+ self
76
+ end
77
+
78
+ # accept :d or :default option
79
+ #
80
+ def self.optdefault(opt_raw)
81
+ return opt_raw[:d] unless opt_raw[:d].nil?
82
+
83
+ opt_raw[:default]
84
+ end
85
+
86
+ def output_help
87
+ puts @values['help']
88
+ end
89
+
90
+ # process arguments as mostly pairs of option name and value
91
+ #
92
+ def parse(argv = ARGV)
93
+ return self if argv.nil? || !(argv&.count || 0).positive?
94
+
95
+ args_ind = 0
96
+ while args_ind < argv.count
97
+ args_consumed = 0
98
+ arg = argv.fetch(args_ind, '') #.tap_inspect 'argument', source: 'EnvOpts'
99
+ if arg.start_with? '--'
100
+ opt_name = arg[2..-1] #.tap_inspect 'opt_name', source: 'EnvOpts'
101
+ args_consumed = consume_arguments(opt_name, argv.fetch(args_ind + 1, nil))
102
+ end
103
+
104
+ if args_consumed.zero?
105
+ if arg == '--help'
106
+ output_help
107
+ exit
108
+ elsif block_given?
109
+ yield 'NAO', [arg]
110
+ args_consumed = 1
111
+ else
112
+ warn "Invalid argument: #{arg.inspect} in #{argv.inspect}"
113
+ exit 1
114
+ end
115
+ end
116
+
117
+ args_ind += args_consumed
118
+ end
119
+ @opts.tap_inspect '@opts'
120
+ @values.tap_inspect '@values'
121
+
122
+ self
123
+ end
124
+
125
+ # set option current values per environment values
126
+ #
127
+ def set_keys_value_per_environment_as_cast(opts_raw)
128
+ return self if opts_raw.nil?
129
+
130
+ opts_raw.each do |key, opt_raw|
131
+ set_key_value_per_environment_as_cast(key_name_to_option_name(key), opt_raw)
132
+ end
133
+ @opts.tap_inspect '@opts'
134
+
135
+ self
136
+ end
137
+
138
+ private
139
+
140
+ # convert key name or symbol to an option name
141
+ #
142
+ def key_name_to_option_name(key)
143
+ (key.is_a?(Symbol) ? symbol_name_to_option_name(key) : key) #.tap_inspect
144
+ end
145
+
146
+ # get cast of environment variable
147
+ #
148
+ def option_cast(opt_raw)
149
+ (opt_raw[:cast].present? ? opt_raw[:cast].to_s : 'to_s').tap_inspect
150
+ end
151
+
152
+ # update value for named option
153
+ # return number of arguments used
154
+ #
155
+ def consume_arguments(opt_name, value)
156
+ return 0 if (opt_raw = @opts.fetch(opt_name, nil)).nil?
157
+
158
+ return 0 unless opt_raw.fetch(:option, true)
159
+
160
+ if !(fixed = opt_raw.fetch(:fixed, nil)).nil?
161
+ set_key_value_as_cast(opt_name, fixed)
162
+ 1
163
+ elsif value.nil?
164
+ 0
165
+ else
166
+ set_key_value_as_cast(opt_name, value)
167
+ 2
168
+ end
169
+ end
170
+
171
+ # option names use hyphens
172
+ #
173
+ def method_name_to_option_name(name)
174
+ name.to_s.gsub('_', '-') #.tap_inspect
175
+ end
176
+
177
+ # read and write options using the option name as a method
178
+ #
179
+ def method_missing(method_name, *args)
180
+ method_name.tap_inspect 'method_name'
181
+ if method_name.to_s.end_with?('=')
182
+ value = args.first
183
+ name = method_name_to_option_name(method_name.to_s[0..-2])
184
+ set_key_value_as_cast(name, value)
185
+ else
186
+ @values[method_name_to_option_name(method_name)]
187
+ end.tap_inspect "ref #{method_name}", source: 'EnvOpts'
188
+ end
189
+
190
+ # option name to environment name
191
+ # if true or empty, compute from option name
192
+ #
193
+ def option_name_to_environment_name(opt_name, opt_raw)
194
+ case env_name = opt_raw.fetch(:env, '')
195
+ when true, ''
196
+ "#{@values['env-prefix']}#{opt_name.upcase.gsub('-', '_')}"
197
+ else
198
+ env_name
199
+ end.tap_inspect
200
+ end
201
+
202
+ # get environment value from option
203
+ #
204
+ def get_environment_value_from_option(opt_name, opt_raw)
205
+ ENV.fetch(option_name_to_environment_name(opt_name, opt_raw), nil).tap_inspect
206
+ end
207
+
208
+ # option names are available as methods
209
+ #
210
+ def respond_to_missing?(method_name, include_private = false)
211
+ (@opts.keys.include?(method_name_to_option_name(method_name)) || super)
212
+ end
213
+
214
+ def set_key_value_as_cast(key, value)
215
+ [key, value].tap_inspect 'key, value'
216
+ opt = @opts[key]
217
+ set_key_value_raw(key, (opt[:cast] ? value.send(opt[:cast]) : value))
218
+ end
219
+
220
+ # set key value_per environment as cast
221
+ #
222
+ def set_key_value_per_environment_as_cast(key, opt_raw)
223
+ key.tap_inspect 'key'
224
+ opt_raw.tap_inspect 'opt_raw'
225
+ return if opt_raw[:env].nil?
226
+
227
+ value = get_environment_value_from_option(key, opt_raw).tap_inspect 'value'
228
+ set_key_value_as_cast(key, opt_raw[:cast] ? value.send(opt_raw[:cast]) : value) unless value.nil?
229
+ end
230
+
231
+ # set key value (raw)
232
+ #
233
+ def set_key_value_raw(key, value)
234
+ [key, value].tap_inspect 'key, value'
235
+ @values[key] = value
236
+ end
237
+
238
+ # symbol name to option name
239
+ # option names use hyphens
240
+ #
241
+ def symbol_name_to_option_name(name)
242
+ name.to_s.gsub('_', '-') #.tap_inspect
243
+ end
244
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ # encoding=utf-8
4
+
5
+ require 'optparse'
6
+ require 'yaml'
7
+
8
+ require_relative 'object_present'
9
+
10
+ # add Hash.sym_keys
11
+ #
12
+ class Hash
13
+ unless defined?(sym_keys)
14
+ def sym_keys
15
+ transform_keys(&:to_sym)
16
+ # map do |key, value|
17
+ # [key.to_sym, value]
18
+ # end.to_h
19
+ end
20
+ end
21
+ end
22
+
23
+ # parse application configuration from command-line options and environment variables
24
+ #
25
+ class EnvironmentOptParse
26
+ attr_reader :options, :remainder
27
+
28
+ # utility functions to create menu
29
+ #
30
+ module Menu
31
+ def menu_all(menu_data, lambdas, config)
32
+ config.tap_yaml 'config'
33
+ input_option_values, remainder, = menu_parse(add_proc(menu_data, lambdas))
34
+ # options = menu_default_option_values(menu_data).merge input_option_values
35
+ # options = (menu_default_option_values(menu_data)).merge(input_option_values).tap_yaml 'options1'
36
+ options = menu_default_option_values(menu_data).merge(config).merge(input_option_values) #.tap_yaml 'options2'
37
+ # options = menu_data.map do |menu_item|
38
+ # menu_item.tap_inspect 'menu_item'
39
+ # mion = menu_item[:opt_name]&.to_sym.tap_inspect 'mion'
40
+ # omion = config[mion].tap_inspect 'omion'
41
+ # unless omion.nil?
42
+ # menu_item[:default] = omion
43
+ # end
44
+ # menu_item
45
+ # end,
46
+
47
+ [options, remainder]
48
+ end
49
+
50
+ def add_proc(menu_data, lambdas)
51
+ menu_data.each do |menu_item|
52
+ menu_item.tap_yaml 'menu_item'
53
+ procname = menu_item[:procname]
54
+ next if procname.nil?
55
+
56
+ menu_item[:proccode] = lambdas.fetch(procname.to_sym, menu_item[:procname])
57
+ end.tap_yaml
58
+ end
59
+
60
+ def menu_default_option_values(menu_data)
61
+ menu_data.map do |item|
62
+ item_default = item[:default]
63
+ next if item_default.nil?
64
+ next unless item[:opt_name].present?
65
+
66
+ [item[:opt_name].to_sym, item_default]
67
+ end.compact.to_h
68
+ end
69
+
70
+ def menu_help(menu_data)
71
+ options = {}
72
+ option_parser = OptionParser.new do |opts|
73
+ opts.banner = [
74
+ "#{APP_NAME} - #{APP_DESC} (#{VERSION})",
75
+ "Usage: #{File.basename($PROGRAM_NAME)} [options]"
76
+ ].join("\n")
77
+
78
+ menu_data.each do |item|
79
+ menu_option_append opts, options, item
80
+ end
81
+ end
82
+
83
+ option_parser.help
84
+ end
85
+
86
+ def menu_option_append(opts, options, item)
87
+ return unless item[:long_name].present? || item[:short_name].present?
88
+
89
+ mmoo = [
90
+ # long name
91
+ if item[:long_name].present?
92
+ "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
93
+ end,
94
+
95
+ # short name
96
+ item[:short_name].present? ? "-#{item[:short_name]}" : nil,
97
+
98
+ # description and default
99
+ [item[:description],
100
+ item[:default].present? ? "[#{value_for_menu item[:default]}]" : nil].compact.join(' '),
101
+
102
+ # apply proccode, if present, to value
103
+ # save value to options hash if option is named
104
+ #
105
+ lambda { |value|
106
+ (item[:proccode] ? item[:proccode].call(value) : value).tap do |converted|
107
+ opt_name = item[:opt_name]
108
+ next if opt_name.nil?
109
+
110
+ options[opt_name.to_sym] = converted if item[:opt_name]
111
+ end
112
+ }
113
+ ].compact
114
+ opts.on(*mmoo)
115
+ end
116
+
117
+ def menu_parse(menu_options)
118
+ options = {}
119
+ option_parser = OptionParser.new do |opts|
120
+ menu_options.each do |item|
121
+ item[:opt_name] = item[:opt_name]&.to_sym
122
+ menu_option_append opts, options, item
123
+ end
124
+ end
125
+ option_parser.load # filename defaults to basename of the program without suffix in a directory ~/.options
126
+ option_parser.environment # env defaults to the basename of the program.
127
+ remainder = option_parser.parse!
128
+
129
+ [options, remainder, option_parser.help]
130
+ end
131
+
132
+ # skip :reek:UtilityFunction
133
+ def value_for_menu(value)
134
+ case value.class.to_s
135
+ when 'String'
136
+ value
137
+ when 'FalseClass', 'TrueClass'
138
+ value ? '1' : '0'
139
+ else
140
+ value.to_s
141
+ end
142
+ end
143
+ end
144
+
145
+ include Menu
146
+
147
+ def initialize(menu: {}, lambdas: nil, options: nil, version: nil)
148
+ @menu = if menu.class.to_s == 'String'
149
+ filetext = File.read(menu).tap_yaml 'filetext'
150
+ fileyaml = YAML.load(filetext)
151
+ fileyaml.map(&:sym_keys)
152
+ else
153
+ menu
154
+ end.tap_yaml '@menu'
155
+ @lambdas = lambdas
156
+ @version = version || '0.1'
157
+ # @options = {}
158
+ @options = if options.class.to_s == 'String'
159
+ YAML.safe_load(File.read(options)).sym_keys.tap_yaml '@options'
160
+ else
161
+ {}
162
+ end #.tap_yaml '@options'
163
+
164
+ parse!
165
+ end
166
+
167
+ def parse!
168
+ @options, @remainder = menu_all(
169
+ @menu,
170
+ # @menu.map do |menu_item|
171
+ # menu_item.tap_inspect 'menu_item'
172
+ # mion = menu_item[:opt_name]&.to_sym.tap_inspect 'mion'
173
+ # omion = @options[mion].tap_inspect 'omion'
174
+ # unless omion.nil?
175
+ # @options[menu_item[:default]] = omion
176
+ # end
177
+ # menu_item
178
+ # end,
179
+ {
180
+ debug: ->(value) { tap_config value: value },
181
+
182
+ # stdout_configuration: lambda { |_| self.options.tap_puts 'eop' },
183
+ # stdout_configuration: (lambda { |options|
184
+ # lambda { |v| options.tap_puts 'eop_l' }
185
+ # }).call(@options),
186
+
187
+ stdout_defaults: ->(_) { menu_default_option_values(@menu).to_yaml.tap_puts },
188
+ stdout_help: lambda { |_|
189
+ menu_help(@menu).tap_puts
190
+ exit
191
+ },
192
+ val_as_bool: ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value },
193
+ val_as_int: ->(value) { value.to_i },
194
+ val_as_str: ->(value) { value.to_s },
195
+ version: lambda { |_|
196
+ @version.tap_puts
197
+ exit
198
+ }
199
+ }.merge(@lambdas || {}),
200
+ @options
201
+ )
202
+ @options #.tap_yaml '@options'
203
+ end
204
+ end
@@ -7,5 +7,5 @@ module MarkdownExec
7
7
  BIN_NAME = 'mde'
8
8
  GEM_NAME = 'markdown_exec'
9
9
  TAP_DEBUG = 'MDE_DEBUG'
10
- VERSION = '1.3.0'
10
+ VERSION = '1.3.2'
11
11
  end