marked-conductor 1.0.27 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab841b7eae48110c76bd6b8b8d86f8f02ddbff220672d80b40e89410c473f9e4
4
- data.tar.gz: c077fa6c66ebad09df4047fc66f5a28b0202045d530de04dad338b707607ba9a
3
+ metadata.gz: 333d82d6ed153da7c528506348ac3faff3852fef0614f0009bbcc0a963907a7f
4
+ data.tar.gz: c24154363d7035cc4bcd1539abe117276f1955231fa0f7af27f49678164cf1db
5
5
  SHA512:
6
- metadata.gz: cfe5a81efc6edf85876b749756fe175c7c6bfe272958c842e19a4f795f8a6339ff4ed32373a339e9921917902d4a589ad5cf3c5bd1f2f8a265c4e2b17326c849
7
- data.tar.gz: 11344262daf5b09c48c7304267c3294125daa65e3e2f98c1e50dbc809bf6981e538304b7229208e9bd7a06bed67804ab2fb575d8105802243ca7c4d048049f8c
6
+ metadata.gz: 323227fde6789d933d412afe287a9784c18ac2610e2d50e5050c14840b8045ce9c2b380fe714aa658c009eaa7dd038b6a6d88f0f648719f4dcd5f0958a862ef2
7
+ data.tar.gz: ebdef901d6a9407adb9cfeb7412f0a21ac01eeeb89f38593556269594e58d32737cf426849d74a9f485e9d865646bbafa16a6b5c25430ed4f98391139d5265e7
data/.irbrc CHANGED
@@ -3,9 +3,9 @@
3
3
  IRB.conf[:AUTO_INDENT] = true
4
4
 
5
5
  require "irb/completion"
6
- # require_relative "lib/conductor"
6
+ require_relative "lib/conductor"
7
7
 
8
- # include Conductor # standard:disable all
8
+ include Conductor # standard:disable all
9
9
 
10
10
  require "awesome_print"
11
11
  AwesomePrint.irb!
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### 1.0.28
2
+
3
+ 2024-07-27 14:54
4
+
5
+ #### IMPROVED
6
+
7
+ - Code cleanup, fix for IRB
8
+ - Switch to using #size for character counting
9
+ - Begin adding test suite, fixing bugs as found
10
+
1
11
  ### 1.0.27
2
12
 
3
13
  2024-07-24 13:55
data/Gemfile CHANGED
@@ -8,3 +8,4 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rubocop", "~> 1.21"
11
+ gem "rspec", "~> 3.13"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- marked-conductor (1.0.25)
4
+ marked-conductor (1.0.28)
5
5
  chronic (~> 0.10.2)
6
6
  tty-which (~> 0.5.0)
7
7
 
@@ -43,10 +43,10 @@ GEM
43
43
  rspec-mocks (~> 3.13.0)
44
44
  rspec-core (3.13.0)
45
45
  rspec-support (~> 3.13.0)
46
- rspec-expectations (3.13.0)
46
+ rspec-expectations (3.13.1)
47
47
  diff-lcs (>= 1.2.0, < 2.0)
48
48
  rspec-support (~> 3.13.0)
49
- rspec-mocks (3.13.0)
49
+ rspec-mocks (3.13.1)
50
50
  diff-lcs (>= 1.2.0, < 2.0)
51
51
  rspec-support (~> 3.13.0)
52
52
  rspec-support (3.13.1)
@@ -93,7 +93,7 @@ DEPENDENCIES
93
93
  parse_gemspec-cli (~> 1.0)
94
94
  pry (~> 0.14.2)
95
95
  rake (~> 13.0)
96
- rspec (~> 3.0)
96
+ rspec (~> 3.13)
97
97
  rubocop (~> 1.21)
98
98
  simplecov (~> 0.21)
99
99
  simplecov-console (~> 0.9)
data/bin/conductor CHANGED
@@ -4,97 +4,6 @@
4
4
  require_relative "../lib/conductor"
5
5
  require "optparse"
6
6
 
7
- # Main Conductor class
8
- module Conductor
9
- class << self
10
- ##
11
- ## Execute commands/scripts in the track
12
- ##
13
- ## @param track The track
14
- ##
15
- ## @return Resulting STDOUT output
16
- ##
17
- def execute_track(track)
18
- if track[:sequence]
19
- track[:sequence].each do |cmd|
20
- if cmd[:script]
21
- script = Script.new(cmd[:script])
22
-
23
- res = script.run
24
- elsif cmd[:command]
25
- command = Command.new(cmd[:command])
26
-
27
- res = command.run
28
- elsif cmd[:filter]
29
- filter = Filter.new(cmd[:filter])
30
-
31
- res = filter.process
32
- end
33
-
34
- Conductor.stdin = res unless res.nil?
35
- end
36
- elsif track[:script]
37
- script = Script.new(track[:script])
38
-
39
- Conductor.stdin = script.run
40
- elsif track[:command]
41
- command = Command.new(track[:command])
42
-
43
- Conductor.stdin = command.run
44
- elsif track[:filter]
45
- filter = Filter.new(track[:filter])
46
-
47
- Conductor.stdin = filter.process
48
- end
49
-
50
- Conductor.stdin
51
- end
52
-
53
- ##
54
- ## Main function to parse conditions and
55
- ## execute actions. Executes recursively for
56
- ## sub-tracks.
57
- ##
58
- ## @param tracks The tracks to process
59
- ## @param res The current result
60
- ## @param condition The current condition
61
- ##
62
- ## @return [Array] result, matched condition(s)
63
- ##
64
- def conduct(tracks, res = nil, condition = nil)
65
- tracks.each do |track|
66
- cond = Condition.new(track[:condition])
67
-
68
- next unless cond.true?
69
-
70
- # Build "matched condition" message
71
- title = track[:title] || track[:condition]
72
- condition ||= [""]
73
- condition << title
74
- condition.push(track.key?(:continue) ? " -> " : ", ")
75
-
76
- res = execute_track(track)
77
-
78
- if track[:tracks]
79
- ts = track[:tracks]
80
-
81
- res, condition = conduct(ts, res, condition)
82
-
83
- next if res.nil?
84
- end
85
-
86
- break unless track[:continue]
87
- end
88
-
89
- if res == Conductor.original_input
90
- [nil, "No change in output"]
91
- else
92
- [res, condition]
93
- end
94
- end
95
- end
96
- end
97
-
98
7
  optparse = OptionParser.new do |opts|
99
8
  opts.banner = "Called from Marked 2 as a Custom Pre/Processor"
100
9
 
@@ -112,6 +21,9 @@ end
112
21
  optparse.parse!
113
22
 
114
23
  config = Conductor::Config.new
24
+ res = config.configure
25
+
26
+ Process.exit 0 unless res
115
27
 
116
28
  Conductor.stdin
117
29
  Conductor.original_input = Conductor.stdin
@@ -132,12 +44,10 @@ if res.nil?
132
44
  warn "No conditions satisfied"
133
45
  # puts Conductor::Env
134
46
  puts "NOCUSTOM"
47
+ elsif res == Conductor.original_input
48
+ warn "No change in output"
49
+ puts "NOCUSTOM"
135
50
  else
136
- if res == Conductor.original_input
137
- warn "No change in output"
138
- puts "NOCUSTOM"
139
- else
140
- warn "Met condition: #{clean_condition(condition)}"
141
- puts res
142
- end
51
+ warn "Met condition: #{clean_condition(condition)}"
52
+ puts res
143
53
  end
@@ -1,22 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Array helpers
3
4
  class ::Array
5
+ ##
6
+ ## Destructive version of #symbolize_keys
7
+ ##
8
+ ## @see #symbolize_keys
9
+ ##
10
+ ## @return [Array] symbolized arrays
11
+ ##
4
12
  def symbolize_keys!
5
13
  replace symbolize_keys
6
14
  end
7
15
 
16
+ ##
17
+ ## Symbolize the keys of an array of hashes
18
+ ##
19
+ ## @return [Array] array of hashes with keys converted to symbols
20
+ ##
8
21
  def symbolize_keys
9
22
  map { |h| h.symbolize_keys }
10
23
  end
11
24
 
25
+ ##
26
+ ## Join components within an array
27
+ ##
28
+ ## @return [Array] array of strings joined by Shellwords
29
+ ##
12
30
  def shell_join
13
31
  map { |p| Shellwords.join(p) }
14
32
  end
15
33
 
34
+ ##
35
+ ## Test if any path in array matches filename
36
+ ##
37
+ ## @param filename [String] The filename
38
+ ##
39
+ ## @return [Boolean] whether file is found
40
+ ##
16
41
  def includes_file?(filename)
17
42
  inc = false
18
43
  each do |path|
19
- if path.join =~ /#{Regexp.escape(filename)}$/i
44
+ path = path.join if path.is_a?(Array)
45
+ if path =~ /#{Regexp.escape(filename)}$/i
20
46
  inc = true
21
47
  break
22
48
  end
@@ -24,10 +50,18 @@ class ::Array
24
50
  inc
25
51
  end
26
52
 
53
+ ##
54
+ ## Test if any path in an array contains any matching fragment
55
+ ##
56
+ ## @param frag [String] The fragment
57
+ ##
58
+ ## @return [Boolean] whether fragment is found
59
+ ##
27
60
  def includes_frag?(frag)
28
61
  inc = false
29
62
  each do |path|
30
- if path.join =~ /#{Regexp.escape(frag)}/i
63
+ path = path.join if path.is_a?(Array)
64
+ if path =~ /#{Regexp.escape(frag)}/i
31
65
  inc = true
32
66
  break
33
67
  end
@@ -2,6 +2,11 @@
2
2
 
3
3
  # True class
4
4
  class ::TrueClass
5
+ ##
6
+ ## If TrueClass, it's a boolean
7
+ ##
8
+ ## @return [Boolean] always true
9
+ ##
5
10
  def bool?
6
11
  true
7
12
  end
@@ -9,6 +14,11 @@ end
9
14
 
10
15
  # False class
11
16
  class ::FalseClass
17
+ ##
18
+ ## If FalseClass, it's a boolean
19
+ ##
20
+ ## @return [Boolean] always true
21
+ ##
12
22
  def bool?
13
23
  true
14
24
  end
@@ -5,14 +5,26 @@ module Conductor
5
5
  class Command
6
6
  attr_reader :args, :path
7
7
 
8
+ ##
9
+ ## Instantiate a command runner
10
+ ##
11
+ ## @param command [String] The command
12
+ ##
8
13
  def initialize(command)
9
14
  parts = Shellwords.split(command)
10
15
  self.path = parts[0]
11
16
  self.args = parts[1..].join(" ")
12
17
  end
13
18
 
19
+ ##
20
+ ## Writer method for command path
21
+ ##
22
+ ## @param path [String] The path
23
+ ##
24
+ ## @return [String] New path
25
+ ##
14
26
  def path=(path)
15
- @path = if %r{^[%/]}.match?(path)
27
+ @path = if %r{^[~/.]}.match?(path)
16
28
  File.expand_path(path)
17
29
  else
18
30
  which = TTY::Which.which(path)
@@ -20,6 +32,13 @@ module Conductor
20
32
  end
21
33
  end
22
34
 
35
+ ##
36
+ ## Writer method for arguments
37
+ ##
38
+ ## @param array [Array] Array of arguments
39
+ ##
40
+ ## @return [String] Arguments as string
41
+ ##
23
42
  def args=(array)
24
43
  @args = if array.is_a?(Array)
25
44
  array.join(" ")
@@ -28,6 +47,11 @@ module Conductor
28
47
  end
29
48
  end
30
49
 
50
+ ##
51
+ ## Run the command
52
+ ##
53
+ ## @return [String] result of running STDIN through command
54
+ ##
31
55
  def run
32
56
  stdin = Conductor.stdin
33
57
 
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Conductor
4
+ # Condition class
4
5
  class Condition
6
+ # R/W condition
7
+ attr_accessor :condition
5
8
 
6
9
  ##
7
10
  ## Initializes the given condition.
@@ -77,6 +80,8 @@ module Conductor
77
80
  value1.to_f > value2.to_f
78
81
  when :lt
79
82
  value1.to_f < value2.to_f
83
+ when :not_contains
84
+ value1.to_s !~ /#{value2}/i
80
85
  when :contains
81
86
  value1.to_s =~ /#{value2}/i
82
87
  when :starts_with
@@ -97,7 +102,7 @@ module Conductor
97
102
  ## @return [Array] Value, value to compare, operator
98
103
  ##
99
104
  def split_condition(condition)
100
- if condition.match(/(?:((?:does )?not)?(?:ha(?:s|ve)|contains?|includes?) +)?(yaml|headers|frontmatter|mmd|meta(?:data)?|pandoc)(:\S+)?/i)
105
+ if condition.match(/(?:((?:does )?not)?(?:ha(?:s|ve)|contains?|includes?) +)?(yaml|headers|frontmatter|mmd|meta(?:data)?|pandoc)(:[\S_ ]+)?/i)
101
106
  m = Regexp.last_match
102
107
  op = m[1].nil? ? :contains : :not_contains
103
108
  type = case m[2]
@@ -111,7 +116,7 @@ module Conductor
111
116
  return ["#{type}#{m[3]}", nil, op]
112
117
  end
113
118
 
114
- res = condition.match(/(?i)^(?<val1>.*?)(?:(?:\s+(?<bool>(?:is|does)?\s*(?:not)?\s*)(?<op>(?:an?|type(?:\sof)?|equals?(?:\sto))?|[!*$]?==?|[gl]t|(?:greater|less)(?:\sthan)?|<|>|(?:starts|ends) with|(?:ha(?:s|ve)\s)?(?:prefix|suffix)|(?:contains?|includes?)\s+(?:file|path)|has|contains?|includes?|match(?:es)?)\s+)(?<val2>.*?))?$/)
119
+ res = condition.match(/(?i)^(?<val1>.*?)(?:(?:\s+(?<bool>(?:is|do(?:es)?)?\s*(?:not)?\s*)(?<op>(?:an?|type(?:\sof)?|equals?(?:\sto))?|[!*$]?==?|[gl]t|(?:greater|less)(?:\sthan)?|<|>|(?:starts|ends) with|(?:ha(?:s|ve)\s)?(?:prefix|suffix)|(?:contains?|includes?)\s+(?:file|path)|has|contains?|includes?|match(?:es)?)\s+)(?<val2>.*?))?$/)
115
120
  operator = res["bool"] ? "#{res["bool"]}#{res["op"]}" : res["op"]
116
121
  [res["val1"], res["val2"], operator_to_symbol(operator)]
117
122
  end
@@ -177,7 +182,7 @@ module Conductor
177
182
 
178
183
  return operator == :not_equal if val1.nil?
179
184
 
180
- val2 = val2.force_encoding("utf-8")
185
+ val2 = val2.to_s.dup.force_encoding("utf-8")
181
186
 
182
187
  if val1.date?
183
188
  if val2.time?
@@ -185,7 +190,7 @@ module Conductor
185
190
  date2 = val2.to_date
186
191
  else
187
192
  date1 = operator == :gt ? val1.to_day(:end) : val1.to_day
188
- date2 = operator == :gt ? val2.to_day(:end) : val1.to_day
193
+ date2 = operator == :gt ? val2.to_day(:end) : val2.to_day
189
194
  end
190
195
 
191
196
  res = case operator
@@ -209,19 +214,19 @@ module Conductor
209
214
  val1 = val1.dup.to_s.force_encoding("utf-8")
210
215
  case operator
211
216
  when :contains
212
- val1 =~ /#{val2}/i
217
+ val1 =~ /#{val2}/i ? true : false
213
218
  when :not_starts_with
214
- val1 !~ /^#{val2}/i
219
+ val1 !~ /^#{val2}/i ? true : false
215
220
  when :not_ends_with
216
- val1 !~ /#{val2}$/i
221
+ val1 !~ /#{val2}$/i ? true : false
217
222
  when :starts_with
218
- val1 =~ /^#{val2}/i
223
+ val1 =~ /^#{val2}/i ? true : false
219
224
  when :ends_with
220
- val1 =~ /#{val2}$/i
225
+ val1 =~ /#{val2}$/i ? true : false
221
226
  when :equal
222
- val1 =~ /^#{val2}$/i
227
+ val1 =~ /^#{val2}$/i ? true : false
223
228
  when :not_equal
224
- val1 !~ /^#{val2}$/i
229
+ val1 !~ /^#{val2}$/i ? true : false
225
230
  else
226
231
  false
227
232
  end
@@ -306,9 +311,9 @@ module Conductor
306
311
 
307
312
  value1 = value1.join(",") if value1.is_a?(Array)
308
313
 
309
- if value1.bool?
314
+ if value1.to_s.bool?
310
315
  test_truthy(value1, value, operator)
311
- elsif value1.number? && value.number? && %i[gt lt equal not_equal].include?(operator)
316
+ elsif value1.to_s.number? && value.to_s.number? && %i[gt lt equal not_equal].include?(operator)
312
317
  test_operator(value1, value, operator)
313
318
  else
314
319
  test_string(value1, value, operator)
@@ -351,6 +356,10 @@ module Conductor
351
356
  end
352
357
 
353
358
  if key
359
+ if %i[type_of not_type_of].include?(operator)
360
+ return test_type(meta[key], value, operator)
361
+ end
362
+
354
363
  test_string(meta[key], value, operator)
355
364
  else
356
365
  res = value ? meta.key?(value) : true
@@ -358,8 +367,14 @@ module Conductor
358
367
  end
359
368
  end
360
369
 
370
+ ##
371
+ ## Test for Pandoc headers
372
+ ##
373
+ ## @param content [String] The content to test
374
+ ## @param operator [Symbol] The operator
375
+ ##
361
376
  def test_pandoc(content, operator)
362
- res = content.match(/^%% /)
377
+ res = content.meta_type == :pandoc
363
378
  %i[not_contains not_equal].include?(operator) ? !res.nil? : res.nil?
364
379
  end
365
380
 
@@ -403,6 +418,13 @@ module Conductor
403
418
  end
404
419
  end
405
420
 
421
+ ##
422
+ ## Convert an operator string to a symbol
423
+ ##
424
+ ## @param operator [String] The operator
425
+ ##
426
+ ## @return [Symbol] the operator symbol
427
+ ##
406
428
  def operator_to_symbol(operator)
407
429
  return operator if operator.nil?
408
430
 
@@ -419,8 +441,6 @@ module Conductor
419
441
  :includes_file
420
442
  when /(ha(?:s|ve)|contains?|includes?|\*=) +path/i
421
443
  :includes_path
422
- when /(ha(?:s|ve)|contains|includes|match(es)?|\*=)/i
423
- :contains
424
444
  when /not (ha(?:s|ve)|contains|includes|match(es)?)/i
425
445
  :not_contains
426
446
  when /(ha(?:s|ve)|contains|includes|match(es)?|\*=)/i
@@ -444,6 +464,11 @@ module Conductor
444
464
  end
445
465
  end
446
466
 
467
+ ##
468
+ ## Parse a condition, handling parens and booleans
469
+ ##
470
+ ## @return [Boolean] condition result
471
+ ##
447
472
  def parse_condition
448
473
  cond = @condition.to_s.gsub(/\((.*?)\)/) do
449
474
  condition = Regexp.last_match(1)
@@ -3,31 +3,60 @@
3
3
  module Conductor
4
4
  # Configuration methods
5
5
  class Config
6
- attr_reader :config, :tracks
6
+ # Configuration
7
+ attr_reader :config
8
+ # Tracks element
9
+ attr_reader :tracks
10
+ # Config file path
11
+ attr_writer :config_file
7
12
 
13
+ ##
14
+ ## Instantiate a configuration
15
+ ##
16
+ ## @return [Config] Config object
17
+ ##
8
18
  def initialize
9
- config_file = File.expand_path("~/.config/conductor/tracks.yaml")
19
+ @config_file = File.expand_path("~/.config/conductor/tracks.yaml")
20
+ end
10
21
 
11
- create_config(config_file) unless File.exist?(config_file)
22
+ def configure
23
+ res = create_config(@config_file)
24
+ return false unless res
12
25
 
13
- @config ||= YAML.safe_load(IO.read(config_file))
26
+ @config ||= YAML.safe_load(IO.read(@config_file))
14
27
 
15
28
  @tracks = @config["tracks"].symbolize_keys
29
+
30
+ return true
16
31
  end
17
32
 
18
33
  private
19
34
 
20
- def create_config(config_file)
35
+ ##
36
+ ## Generate a blank config and directory structure
37
+ ##
38
+ ## @param config_file [String] The configuration file to create
39
+ ##
40
+ def create_config(config_file = nil)
41
+ config_file ||= @config_file
21
42
  config_dir = File.dirname(config_file)
22
43
  scripts_dir = File.dirname(File.join(config_dir, "scripts"))
23
44
  FileUtils.mkdir_p(config_dir) unless File.directory?(config_dir)
24
45
  FileUtils.mkdir_p(scripts_dir) unless File.directory?(scripts_dir)
25
- File.open(config_file, "w") { |f| f.puts sample_config }
26
- puts "Sample config created at #{config_file}"
46
+ unless File.exist?(config_file)
47
+ File.open(config_file, "w") { |f| f.puts sample_config }
48
+ puts "Sample config created at #{config_file}"
49
+ return false
50
+ end
27
51
 
28
- Process.exit 0
52
+ return true
29
53
  end
30
54
 
55
+ ##
56
+ ## Content for sample configuration
57
+ ##
58
+ ## @return [String] sample config
59
+ ##
31
60
  def sample_config
32
61
  <<~EOCONFIG
33
62
  tracks:
data/lib/conductor/env.rb CHANGED
@@ -14,7 +14,7 @@ module Conductor
14
14
  home: ENV["HOME"],
15
15
  css_path: ENV["MARKED_CSS_PATH"],
16
16
  ext: ENV["MARKED_EXT"],
17
- includes: ENV["MARKED_INCLUDES"].split(/,/).map { |s| Shellwords.shellsplit(s) },
17
+ includes: ENV["MARKED_INCLUDES"].split_list,
18
18
  origin: ENV["MARKED_ORIGIN"],
19
19
  filepath: ENV["MARKED_PATH"],
20
20
  filename: File.basename(ENV["MARKED_PATH"]),
@@ -35,7 +35,7 @@ module Conductor
35
35
  home: "/Users/ttscoff",
36
36
  css_path: "/Applications/Marked 2.app/Contents/Resources/swiss.css",
37
37
  ext: "md",
38
- includes: [],
38
+ includes: "".split_list,
39
39
  origin: "/Users/ttscoff/Sites/dev/bt/source/_posts/",
40
40
  filepath: "/Users/ttscoff/Sites/dev/bt/source/_posts/2024-04-01-automating-the-dimspirations-workflow.md",
41
41
  filename: "advanced-features.md",
@@ -46,7 +46,7 @@ module Conductor
46
46
  end
47
47
 
48
48
  ##
49
- ## env to string
49
+ ## env to shell-compatible string
50
50
  ##
51
51
  ## @return [String] shell-compatible string representation of @env
52
52
  ##