marked-conductor 1.0.27 → 1.0.28

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: 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
  ##