marked-conductor 1.0.7 → 1.0.8

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: 3b5faacd380f833b615f2f848ed6f0bcb54dd955082b71228ab4f49c9e2bf0ac
4
- data.tar.gz: 7c513f2fc4f75094e99ea27c48cf545ca738abe366271ec3da216f92ff0a4a5e
3
+ metadata.gz: fc01f58de2ca4d81c45420e08128c3ae0dd3f125badce5d62a8cc568c599d202
4
+ data.tar.gz: ad0724664a346aea50e4467d7a492737d885b6b2916f04077a73866b3ba0e915
5
5
  SHA512:
6
- metadata.gz: c84b488b4cc93effd14039e59be3ea76ce4d8e0c5ab882ceb542e93d158fb82d11b5f46bfcd60d5942a265d6554107fa964bb8acafe2d9ced9e8302ea7eb10a6
7
- data.tar.gz: 7e80a80708878d3ab4dbc515d5554c77af6bdd42b8ceb496c2b16bb48b054ea8696aa57351eeae0a35adb3de00ef2dce6c509e77c850aa8ebb4a71c2d672a4f2
6
+ metadata.gz: b812adad30a746bc2b38f302047a595796d8ec2886b503a198b1384dd570d0b61d41de1029788c95b3f6a579c98c4571bef01c00c8f866aeb6245028676ab104
7
+ data.tar.gz: 11f506aa92cdc7ea0c5f219b84d694615c32da729ba57dfafa0f4eb9aa258d33c69c36383d858b0d0764c03362eb7e6db0b3fe4862b441db3d326e5c5f678f36
data/.irbrc CHANGED
@@ -1,3 +1,9 @@
1
- require_relative 'lib/conductor'
2
- include Conductor
1
+ require "irb/completion"
2
+ require_relative "lib/conductor"
3
3
 
4
+ include Conductor # standard:disable all
5
+
6
+ IRB.conf[:AUTO_INDENT] = true
7
+
8
+ require "awesome_print"
9
+ AwesomePrint.irb!
data/.rubocop.yml CHANGED
@@ -3,11 +3,11 @@ AllCops:
3
3
 
4
4
  Style/StringLiterals:
5
5
  Enabled: true
6
- EnforcedStyle: single_quotes
6
+ EnforcedStyle: double_quotes
7
7
 
8
8
  Style/StringLiteralsInInterpolation:
9
9
  Enabled: true
10
- EnforcedStyle: single_quotes
10
+ EnforcedStyle: double_quotes
11
11
 
12
12
  Layout/LineLength:
13
13
  Max: 120
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ### 1.0.8
2
+
3
+ 2024-04-27 14:01
4
+
5
+ #### NEW
6
+
7
+ - Add sequence: key to allow running a series of scripts/commands, each piping to the next
8
+ - Add `continue: true` for tracks to allow processing to continue after a script/command is successful
9
+ - `filename` key for comparing to just filename (instead of full
10
+ - Add `is a` tests for `number`, `integer`, and `float`
11
+ - Tracks in YAML config can have a title key that will be shown in STDERR 'Conditions met:' output
12
+ - Add `does not contain` handling for string and metadata comparisons
13
+
14
+ #### IMPROVED
15
+
16
+ - Allow `has yaml` or `has meta` (MultiMarkdown) as conditions
17
+
18
+ #### FIXED
19
+
20
+ - Use STDIN instead of reading file for conditionals
21
+ - String tests read STDIN input, not reading the file itself, allowing for piping between multiple scripts
22
+
1
23
  ### 1.0.7
2
24
 
3
25
  2024-04-26 11:53
data/Gemfile.lock CHANGED
@@ -1,22 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- marked-conductor (1.0.5)
4
+ marked-conductor (1.0.8)
5
5
  chronic (~> 0.10.2)
6
6
  tty-which (~> 0.5.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
+ ansi (1.5.0)
11
12
  ast (2.4.2)
12
13
  awesome_print (1.9.2)
13
14
  chronic (0.10.2)
14
15
  coderay (1.1.3)
16
+ diff-lcs (1.5.1)
17
+ docile (1.4.0)
18
+ gem-release (2.2.2)
15
19
  json (2.7.2)
16
20
  language_server-protocol (3.17.0.3)
17
21
  method_source (1.1.0)
22
+ multi_json (1.15.0)
18
23
  parallel (1.24.0)
19
- parser (3.3.0.5)
24
+ parse_gemspec (1.0.0)
25
+ parse_gemspec-cli (1.0.0)
26
+ multi_json
27
+ parse_gemspec
28
+ thor
29
+ parser (3.3.1.0)
20
30
  ast (~> 2.4.1)
21
31
  racc
22
32
  pry (0.14.2)
@@ -27,7 +37,20 @@ GEM
27
37
  rake (13.2.1)
28
38
  regexp_parser (2.9.0)
29
39
  rexml (3.2.6)
30
- rubocop (1.63.3)
40
+ rspec (3.13.0)
41
+ rspec-core (~> 3.13.0)
42
+ rspec-expectations (~> 3.13.0)
43
+ rspec-mocks (~> 3.13.0)
44
+ rspec-core (3.13.0)
45
+ rspec-support (~> 3.13.0)
46
+ rspec-expectations (3.13.0)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.13.0)
49
+ rspec-mocks (3.13.0)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.13.0)
52
+ rspec-support (3.13.1)
53
+ rubocop (1.62.1)
31
54
  json (~> 2.3)
32
55
  language_server-protocol (>= 3.17.0)
33
56
  parallel (~> 1.10)
@@ -41,18 +64,39 @@ GEM
41
64
  rubocop-ast (1.31.2)
42
65
  parser (>= 3.3.0.4)
43
66
  ruby-progressbar (1.13.0)
67
+ simplecov (0.22.0)
68
+ docile (~> 1.1)
69
+ simplecov-html (~> 0.11)
70
+ simplecov_json_formatter (~> 0.1)
71
+ simplecov-console (0.9.1)
72
+ ansi
73
+ simplecov
74
+ terminal-table
75
+ simplecov-html (0.12.3)
76
+ simplecov_json_formatter (0.1.4)
77
+ terminal-table (3.0.2)
78
+ unicode-display_width (>= 1.1.1, < 3)
79
+ thor (1.3.1)
44
80
  tty-which (0.5.0)
45
81
  unicode-display_width (2.5.0)
82
+ yard (0.9.36)
46
83
 
47
84
  PLATFORMS
48
85
  arm64-darwin-20
49
86
 
50
87
  DEPENDENCIES
51
88
  awesome_print (~> 1.9.2)
89
+ bundler (~> 2.0)
90
+ gem-release (~> 2.2)
52
91
  marked-conductor!
92
+ parse_gemspec-cli (~> 1.0)
53
93
  pry (~> 0.14.2)
54
94
  rake (~> 13.0)
95
+ rspec (~> 3.0)
55
96
  rubocop (~> 1.21)
97
+ simplecov (~> 0.21)
98
+ simplecov-console (~> 0.9)
99
+ yard (~> 0.9, >= 0.9.26)
56
100
 
57
101
  BUNDLED WITH
58
102
  2.2.29
data/README.md CHANGED
@@ -64,6 +64,26 @@ tracks:
64
64
  command: obsidian-md-filter
65
65
  ```
66
66
 
67
+ #### Adding a title
68
+
69
+ Tracks can contain a `title` key. This is only used in the STDERR output of the track, where 'Met condition: ...' is shown for debugging. If a title is not present, the condition itself will be shown for debugging. If a title is defined, it replaces the condition in the STDERR output. This is mostly for shortening long condition strings to something more meaningful for debugging.
70
+
71
+ ### Sequencing
72
+
73
+ A track can also contain a sequence of scripts and/or commands. STDIN will be passed into the first script/command, then the STDOUT of that will be piped to the next script/command. To do this, add a key called `sequence` that contains an array of scripts and commands:
74
+
75
+ ```yaml
76
+ tracks:
77
+ - condition: phase is pro AND path contains README.md
78
+ sequence:
79
+ - script: strip_emoji
80
+ - command: rdiscount
81
+ ```
82
+
83
+ A sequence can not contain nested tracks.
84
+
85
+ By default, processing stops when a condition is met. If you want to continue processing after a condition is successful, add the `continue: true` to the track. This will only apply to tracks containing this key, and processing will stop when it gets to a successful condition that doesn't contain the `continue` key (or reaches the end of the tracks without another match).
86
+
67
87
  ### Conditions
68
88
 
69
89
  Available conditions are:
@@ -71,6 +91,7 @@ Available conditions are:
71
91
  - `extension` (or `ext`): This will test the extension of the file, e.g. `ext is md` or `ext contains task`
72
92
  - `tree contains ...`: This will test whether a given file or directory exists in any of the parent folders of the current file, starting with the current directory of the file. Example: `tree contains .obsidian` would test whether there was an `.obsidian` directory in any of the directories above the file (indicating it's within an Obsidian vault)
73
93
  - `path`: This tests just the path to the file itself, allowing conditions like `path contains _drafts` or `path does not contain _posts`.
94
+ - `filename`: Tests only the filename, can be any string comparison (`starts with`, `is`, `contains`, etc.).
74
95
  - `phase`: Tests whether Marked is in Preprocessor or Processor phase, allowing conditions like `phase is preprocess` or `phase is process` (which can be shortened to `pre` and `pro`).
75
96
  - `text`: This tests for any string match within the text of the document being processed. This can be used with operators `starts with`, `ends with`, or `contains`, e.g. `text contains @taskpaper` or `text does not contain <!--more-->`.
76
97
  - If the test value is surrounded by forward slashes, it will be treated as a regular expression. Regexes are always flagged as case insensitive. Use it like `text contains /@\w+/`.
@@ -82,7 +103,7 @@ Available conditions are:
82
103
  - If the YAML key is a date, it can be tested against with `before`, `after`, and `is`, and the value can be a natural language date, e.g. `yaml:date is after may 3, 2024`
83
104
  - If both the YAML key value and the test value are numbers, you can use operators `greater than` (`>`), `less than` (`<`), `equal`/`is` (`=`/`==`), and `is not equal`/`not equals` (`!=`/`!==`). Numbers will be interpreted as floats.
84
105
  - If the YAML value is a boolean, you can test with `is true` or `is not true` (or `is false`)
85
- - `mmd` or `meta` will test for MultiMarkdown metadata using the same formatting as `yaml`.
106
+ - `mmd` or `meta` will test for MultiMarkdown metadata using the same formatting as `yaml` above.
86
107
  - The following keywords act as a catchall and can be used as the last track in the config to act on any documents that aren't matched by preceding rules:
87
108
  - `any`
88
109
  - `else`
@@ -115,6 +136,9 @@ All of the [capabilities and requirements](https://marked2app.com/help/Custom_Pr
115
136
 
116
137
  A script run by Conductor already knows it has the right type of file with the expected data and path, so your script can focus on just processing one file type. It's recommended to separate all of that logic you may already have written out into separate scripts and let Conductor handle the forking based on various criteria.
117
138
 
139
+ > Custom processors **must** wait for input on STDIN. Most markdown CLIs will do this automatically, but scripts should include a call to read STDIN. This will pause the script and wait for the data to be sent. Without this, Marked will launch the script, and if it closes the pipe, it will try to write data to a closed pipe and crash immediately. This is a very difficult error to trap in Marked, so it's crucial that all scripts keep the STDIN pipe open.
140
+
141
+
118
142
  ## Tips
119
143
 
120
144
  - Config file must be valid YAML. Any value containing colons, brackets, or other special characters should be quoted, e.g. (`condition: "text contains my:text"`)
@@ -124,6 +148,12 @@ A script run by Conductor already knows it has the right type of file with the e
124
148
 
125
149
  ## Testing
126
150
 
151
+ You can test conductor setups using Marked's `Help->Show Custom Processor Log`, or by running from the command line. The easiest way to test conditions is to set the track's command to `echo "meaningful definition"` and see what conditions are met when conductor is run.
152
+
153
+ In Marked's Custom Processor Log, you can see both the STDOUT output and the STDERR messages. When running Conductor, the STDERR output will show what conditions were met (as well as any errors reported).
154
+
155
+ ### From the command line
156
+
127
157
  In order to test from the command line, you'll need certain environment variables set. This can be done by exporting the following variables with your own definitions, or by running conductor with all of the variables preceding the command, e.g. `$ MARKED_ORIGIN=/path/to/markdown_file.md [...] conductor`.
128
158
 
129
159
  The following need to be defined. Some can be left as empty or to defaults, such as `MARKED_INCLUDES` and `MARKED_OUTLINE`, but all need to be set to something.
data/README.rdoc ADDED
@@ -0,0 +1,6 @@
1
+ = Marked Commander
2
+
3
+ A command line tool that functions as a custom processor handler for Marked 2.
4
+
5
+
6
+
data/Rakefile CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rubocop/rake_task"
5
+ require "rspec/core/rake_task"
6
+ require "rdoc/task"
7
+ require "yard"
8
+
9
+ Rake::RDocTask.new do |rd|
10
+ rd.main = "README.rdoc"
11
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb", "bin/**/*")
12
+ rd.title = "Marked Conductor"
13
+ end
14
+
15
+ YARD::Rake::YardocTask.new do |t|
16
+ t.files = ["lib/conductor/*.rb"]
17
+ t.options = ["--markup-provider=redcarpet", "--markup=markdown", "--no-private", "-p", "yard_templates"]
18
+ # t.stats_options = ['--list-undoc']
19
+ end
20
+
21
+ RSpec::Core::RakeTask.new(:spec) do |t|
22
+ t.rspec_opts = "--pattern spec/*_spec.rb"
23
+ end
24
+
25
+ task default: %i[test]
26
+
27
+ desc "Alias for build"
28
+ task package: :build
29
+
30
+ task test: "spec"
31
+ task lint: "standard"
32
+ task format: "standard:fix"
33
+
34
+ desc "Open an interactive ruby console"
35
+ task :console do
36
+ require "irb"
37
+ require "bundler/setup"
38
+ require "conductor"
39
+ ARGV.clear
40
+ IRB.start
41
+ end
5
42
 
6
43
  RuboCop::RakeTask.new
7
44
 
@@ -13,7 +50,7 @@ task package: :build
13
50
  desc "Development version check"
14
51
  task :ver do
15
52
  gver = `git ver`
16
- cver = IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
53
+ cver = IO.read(File.join(File.dirname(__FILE__), "CHANGELOG.md")).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
17
54
  res = `grep VERSION lib/conductor/version.rb`
18
55
  version = res.match(/VERSION *= *['"](\d+\.\d+\.\d+(\w+)?)/)[1]
19
56
  puts "git tag: #{gver}"
data/bin/conductor CHANGED
@@ -12,38 +12,67 @@ config = Config.new
12
12
 
13
13
  stdin = Conductor.stdin
14
14
 
15
+ def execute_track(track)
16
+ if track[:sequence]
17
+ track[:sequence].each do |cmd|
18
+ if cmd[:script]
19
+ script = Script.new(cmd[:script])
20
+
21
+ res = script.run
22
+ elsif cmd[:command]
23
+ command = Command.new(cmd[:command])
24
+
25
+ res = command.run
26
+ end
27
+
28
+ Conductor.stdin = res unless res.nil?
29
+ end
30
+ elsif track[:script]
31
+ script = Script.new(track[:script])
32
+
33
+ Conductor.stdin = script.run
34
+ elsif track[:command]
35
+ command = Command.new(track[:command])
36
+
37
+ Conductor.stdin = command.run
38
+ end
39
+
40
+ Conductor.stdin
41
+ end
42
+
15
43
  def conduct(tracks, res = nil, condition = nil)
16
44
  tracks.each do |track|
17
45
  cond = Condition.new(track[:condition])
18
46
 
19
47
  next unless cond.true?
20
48
 
21
- if track[:tracks]
22
- ts = track[:tracks].symbolize_keys
23
-
24
- res, condition = conduct(ts, res, condition)
25
49
 
26
- next if res.nil?
27
- end
50
+ # Build "matched condition" message
51
+ title = track[:title] || track[:condition]
52
+ condition ||= ['']
53
+ condition << title
54
+ condition.push(track.key?(:continue) ? ', ' : ' -> ')
28
55
 
29
- condition = condition.nil? ? track[:condition] : "#{track[:condition]} > #{condition}"
56
+ res = execute_track(track)
30
57
 
31
- if track[:script]
32
- script = Script.new(track[:script])
58
+ if track[:tracks]
59
+ ts = track[:tracks]
33
60
 
34
- res = script.run
35
- elsif track[:command]
36
- command = Command.new(track[:command])
61
+ res, condition = conduct(ts, res, condition)
37
62
 
38
- res = command.run
63
+ next if res.nil?
39
64
  end
40
65
 
41
- break
66
+ break unless track[:continue]
42
67
  end
43
68
 
44
69
  [res, condition]
45
70
  end
46
71
 
72
+ def clean_condition(condition)
73
+ condition.join('').sub(/ *(->|,) *$/, '')
74
+ end
75
+
47
76
  options = {}
48
77
  optparse = OptionParser.new do|opts|
49
78
  opts.banner = "Called from Marked 2 as a Custom Pre/Processor"
@@ -68,7 +97,7 @@ if res.nil?
68
97
  $stderr.puts('No conditions satisfied')
69
98
  puts 'NOCUSTOM'
70
99
  else
71
- $stderr.puts("Met condition: #{condition}")
100
+ $stderr.puts("Met condition: #{clean_condition(condition)}")
72
101
  puts res
73
102
  end
74
103
 
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ::Array
4
-
5
4
  def symbolize_keys!
6
5
  replace symbolize_keys
7
6
  end
@@ -8,37 +8,37 @@ module Conductor
8
8
  def initialize(command)
9
9
  parts = Shellwords.split(command)
10
10
  self.path = parts[0]
11
- self.args = parts[1..].join(' ')
11
+ self.args = parts[1..].join(" ")
12
12
  end
13
13
 
14
14
  def path=(path)
15
- @path = if path =~ %r{^[%/]}
16
- File.expand_path(path)
17
- else
18
- which = TTY::Which.which(path)
19
- which || path
20
- end
15
+ @path = if %r{^[%/]}.match?(path)
16
+ File.expand_path(path)
17
+ else
18
+ which = TTY::Which.which(path)
19
+ which || path
20
+ end
21
21
  end
22
22
 
23
23
  def args=(array)
24
24
  @args = if array.is_a?(Array)
25
- array.join(' ')
26
- else
27
- array
28
- end
25
+ array.join(" ")
26
+ else
27
+ array
28
+ end
29
29
  end
30
30
 
31
31
  def run
32
32
  stdin = Conductor.stdin
33
33
 
34
- raise 'Command path not found' unless @path
34
+ raise "Command path not found" unless @path
35
35
 
36
36
  use_stdin = true
37
- if args =~ /\$\{?file\}?/
37
+ if /\$\{?file\}?/.match?(args)
38
38
  use_stdin = false
39
39
  args.sub!(/\$\{?file\}?/, %("#{Env.env[:filepath]}"))
40
40
  else
41
- raise 'No input' unless stdin
41
+ raise "No input" unless stdin
42
42
 
43
43
  end
44
44
 
@@ -11,6 +11,13 @@ module Conductor
11
11
  parse_condition
12
12
  end
13
13
 
14
+ ##
15
+ ## @brief Splits booleans and tests components.
16
+ ##
17
+ ## @param condition The condition to test
18
+ ##
19
+ ## @return [Boolean] test result
20
+ ##
14
21
  def split_booleans(condition)
15
22
  split = condition.split(/ ((?:AND )?NOT|AND|OR) /)
16
23
 
@@ -21,7 +28,7 @@ module Conductor
21
28
  bool = nil
22
29
  prev = false
23
30
  split.each do |cond|
24
- if cond =~ /((?:AND )?NOT|AND|OR)/
31
+ if /((?:AND )?NOT|AND|OR|&&|\|\||!!)/.match?(cond)
25
32
  bool = cond.bool_to_symbol
26
33
  next
27
34
  end
@@ -44,6 +51,15 @@ module Conductor
44
51
  end
45
52
  end
46
53
 
54
+ ##
55
+ ## @brief Test operators
56
+ ##
57
+ ## @param value1 Value
58
+ ## @param value2 Value to test
59
+ ## @param operator The operator
60
+ ##
61
+ ## @return [Boolean] test result
62
+ ##
47
63
  def test_operator(value1, value2, operator)
48
64
  case operator
49
65
  when :gt
@@ -63,13 +79,38 @@ module Conductor
63
79
  end
64
80
  end
65
81
 
82
+ ##
83
+ ## @brief Splits a natural language condition.
84
+ ##
85
+ ## @param condition The condition
86
+ ## @return [Array] Value, value to compare, operator
87
+ ##
66
88
  def split_condition(condition)
89
+ if condition.match(/(?:((?:does )?not)?(?:ha(?:s|ve)|contains?|includes?) +)?(yaml|headers|frontmatter|mmd|meta(?:data)?)(:\S+)?/i)
90
+ m = Regexp.last_match
91
+ op = m[1].nil? ? :contains : :not_contains
92
+ type = m[2] =~ /^m/i ? "mmd" : "yaml"
93
+ return ["#{type}#{m[3]}", nil, op]
94
+ end
67
95
  res = condition.match(/^(?<val1>.*?)(?:(?: +(?<op>(?:is|does)(?: not)?(?: an?|type(?: of)?|equals?(?: to))?|!?==?|[gl]t|(?:greater|less)(?: than)?|<|>|(?:starts|ends) with|(?:ha(?:s|ve) )?(?:prefix|suffix)|has|contains?|includes?) +)(?<val2>.*?))?$/i)
68
- [res['val1'], res['val2'], operator_to_symbol(res['op'])]
96
+ [res["val1"], res["val2"], operator_to_symbol(res["op"])]
69
97
  end
70
98
 
99
+ ##
100
+ ## @brief Test for type of value
101
+ ##
102
+ ## @param val1 value
103
+ ## @param val2 value to test against
104
+ ## @param operator The operator
105
+ ##
71
106
  def test_type(val1, val2, operator)
72
107
  res = case val2
108
+ when /number/
109
+ val1.is_a?(Numeric)
110
+ when /int(eger)?/
111
+ val1.is_a?(Integer)
112
+ when /(float|decimal)/
113
+ val1.is_a?(Float)
73
114
  when /array/i
74
115
  val1.is_a?(Array)
75
116
  when /(string|text)/i
@@ -80,12 +121,21 @@ module Conductor
80
121
  operator == :type_of ? res : !res
81
122
  end
82
123
 
124
+ ##
125
+ ## @brief Compare a string based on operator
126
+ ##
127
+ ## @param val1 The string to test against
128
+ ## @param val2 The value to test
129
+ ## @param operator The operator
130
+ ##
131
+ ## @return [Boolean] test result
132
+ ##
83
133
  def test_string(val1, val2, operator)
84
134
  return operator == :not_equal ? val1.nil? : !val1.nil? if val2.nil?
85
135
 
86
136
  return operator == :not_equal if val1.nil?
87
137
 
88
- val2 = val2.force_encoding('utf-8')
138
+ val2 = val2.force_encoding("utf-8")
89
139
 
90
140
  if val1.date?
91
141
  if val2.time?
@@ -109,12 +159,12 @@ module Conductor
109
159
  return res unless res.nil?
110
160
  end
111
161
 
112
- val2 = if val2.strip =~ %r{^/.*?/$}
113
- val2.gsub(%r{(^/|/$)}, '')
162
+ val2 = if %r{^/.*?/$}.match?(val2.strip)
163
+ val2.gsub(%r{(^/|/$)}, "")
114
164
  else
115
165
  Regexp.escape(val2)
116
166
  end
117
- val1 = val1.dup.to_s.force_encoding('utf-8')
167
+ val1 = val1.dup.to_s.force_encoding("utf-8")
118
168
  case operator
119
169
  when :contains
120
170
  val1 =~ /#{val2}/i
@@ -135,6 +185,17 @@ module Conductor
135
185
  end
136
186
  end
137
187
 
188
+ ##
189
+ ## @brief Test for the existince of a
190
+ ## file/directory in the parent tree
191
+ ##
192
+ ## @param origin Starting directory
193
+ ## @param value The file/directory to search
194
+ ## for
195
+ ## @param operator The operator
196
+ ##
197
+ ## @return [Boolean] test result
198
+ ##
138
199
  def test_tree(origin, value, operator)
139
200
  return true if File.exist?(File.join(origin, value))
140
201
 
@@ -142,13 +203,22 @@ module Conductor
142
203
 
143
204
  if Dir.exist?(File.join(dir, value))
144
205
  true
145
- elsif [Dir.home, '/'].include?(dir)
206
+ elsif [Dir.home, "/"].include?(dir)
146
207
  false
147
208
  else
148
209
  test_tree(dir, value, operator)
149
210
  end
150
211
  end
151
212
 
213
+ ##
214
+ ## @brief Test "truthiness"
215
+ ##
216
+ ## @param value1 Value to test against
217
+ ## @param value2 Value to test
218
+ ## @param operator The operator
219
+ ##
220
+ ## @return [Boolean] test result
221
+ ##
152
222
  def test_truthy(value1, value2, operator)
153
223
  return false unless value2&.bool?
154
224
 
@@ -159,34 +229,64 @@ module Conductor
159
229
  operator == :not_equal ? !res : res
160
230
  end
161
231
 
232
+ ##
233
+ ## @brief Test for presence of yaml, optionall for
234
+ ## a key, optionally for a key's value
235
+ ##
236
+ ## @param content Text content containing YAML
237
+ ## @param value The value to test for
238
+ ## @param key The key to test for
239
+ ## @param operator The operator
240
+ ##
241
+ ## @return [Boolean] test result
242
+ ##
162
243
  def test_yaml(content, value, key, operator)
163
244
  yaml = YAML.safe_load(content.split(/^(?:---|\.\.\.)/)[1])
164
245
 
165
- return operator == :not_equal ? true : false unless yaml
246
+ return operator == :not_equal unless yaml
166
247
 
167
248
  if key
168
249
  value1 = yaml[key]
169
- return operator == :not_equal ? true : false if value1.nil?
250
+ return operator == :not_equal if value1.nil?
251
+
252
+ if value.nil?
253
+ has_key = !value1.nil?
254
+ return operator == :not_equal ? !has_key : has_key
255
+ end
170
256
 
171
- value1 = value1.join(',') if value1.is_a?(Array)
172
257
  if %i[type_of not_type_of].include?(operator)
173
- test_type(value1, value, operator)
174
- elsif value1.bool?
258
+ return test_type(value1, value, operator)
259
+ end
260
+
261
+ value1 = value1.join(",") if value1.is_a?(Array)
262
+
263
+ if value1.bool?
175
264
  test_truthy(value1, value, operator)
176
- elsif value1.number? && value2.number? && %i[gt lt equal not_equal].include?(operator)
265
+ elsif value1.number? && value.number? && %i[gt lt equal not_equal].include?(operator)
177
266
  test_operator(value1, value, operator)
178
267
  else
179
268
  test_string(value1, value, operator)
180
269
  end
181
270
  else
182
271
  res = value ? yaml.key?(value) : true
183
- operator == :not_equal ? !res : res
272
+ (operator == :not_equal) ? !res : res
184
273
  end
185
274
  end
186
275
 
276
+ ##
277
+ ## @brief Test for MultiMarkdown metadata,
278
+ ## optionally key and value
279
+ ##
280
+ ## @param content [String] The text content
281
+ ## @param value [String] The value to test for
282
+ ## @param key [String] The key to test for
283
+ ## @param operator [Symbol] The operator
284
+ ##
285
+ ## @return [Boolean] test result
286
+ ##
187
287
  def test_meta(content, value, key, operator)
188
288
  headers = []
189
- content.split(/\n/).each do |line|
289
+ content.split("\n").each do |line|
190
290
  break if line == /^ *\n$/ || line !~ /\w+: *\S/
191
291
 
192
292
  headers << line
@@ -199,8 +299,8 @@ module Conductor
199
299
  meta = {}
200
300
  headers.each do |h|
201
301
  parts = h.split(/ *: */)
202
- k = parts[0].strip.downcase.gsub(/ +/, '')
203
- v = parts[1..].join(':').strip
302
+ k = parts[0].strip.downcase.gsub(/ +/, "")
303
+ v = parts[1..].join(":").strip
204
304
  meta[k] = v
205
305
  end
206
306
 
@@ -231,26 +331,18 @@ module Conductor
231
331
  test_tree(@env[:origin], value, operator)
232
332
  when /^(path|dir)/i
233
333
  test_string(@env[:filepath], value, operator) ? true : false
334
+ when /^(file)?name/i
335
+ test_string(@env[:filename], value, operator) ? true : false
234
336
  when /^phase/i
235
337
  test_string(@env[:phase], value, :starts_with) ? true : false
236
338
  when /^text/i
237
- test_string(IO.read(@env[:filepath]).force_encoding('utf-8'), value, operator) ? true : false
238
- when /^(yaml|headers|frontmatter)(?::(.*?))?$/i
239
- m = Regexp.last_match
240
-
241
- key = m[2] || nil
242
-
243
- content = IO.read(@env[:filepath]).force_encoding('utf-8')
244
-
245
- content.yaml? ? test_yaml(content, value, key, operator) : false
246
- when /^(mmd|meta(?:data)?)(?::(.*?))?$/i
247
- m = Regexp.last_match
248
-
249
- key = m[2] || nil
250
-
251
- content = IO.read(@env[:filepath]).force_encoding('utf-8')
252
-
253
- content.meta? ? test_meta(content, value, key, operator) : false
339
+ test_string(Conductor.stdin, value, operator) ? true : false
340
+ when /^(?:yaml|headers|frontmatter)(?::(.*?))?$/i
341
+ key = Regexp.last_match(1) || nil
342
+ Conductor.stdin.yaml? ? test_yaml(Conductor.stdin, value, key, operator) : false
343
+ when /^(?:mmd|meta(?:data)?)(?::(.*?))?$/i
344
+ key = Regexp.last_match(1) || nil
345
+ Conductor.stdin.meta? ? test_meta(Conductor.stdin, value, key, operator) : false
254
346
  else
255
347
  false
256
348
  end
@@ -264,6 +356,8 @@ module Conductor
264
356
  :gt
265
357
  when /(lt|less( than)?|<|(?:is )?before)/i
266
358
  :lt
359
+ when /not (ha(?:s|ve)|contains|includes|match(es)?|\*=)/i
360
+ :not_contains
267
361
  when /(ha(?:s|ve)|contains|includes|match(es)?|\*=)/i
268
362
  :contains
269
363
  when /not (suffix|ends? with)/i
@@ -6,22 +6,22 @@ module Conductor
6
6
  attr_reader :config, :tracks
7
7
 
8
8
  def initialize
9
- config_file = File.expand_path('~/.config/conductor/tracks.yaml')
9
+ config_file = File.expand_path("~/.config/conductor/tracks.yaml")
10
10
 
11
11
  create_config(config_file) unless File.exist?(config_file)
12
12
 
13
13
  @config ||= YAML.safe_load(IO.read(config_file))
14
14
 
15
- @tracks = @config['tracks'].symbolize_keys
15
+ @tracks = @config["tracks"].symbolize_keys
16
16
  end
17
17
  end
18
18
 
19
19
  def create_config(config_file)
20
20
  config_dir = File.dirname(config_file)
21
- scripts_dir = File.dirname(File.join(config_dir, 'scripts'))
21
+ scripts_dir = File.dirname(File.join(config_dir, "scripts"))
22
22
  FileUtils.mkdir_p(config_dir) unless File.directory?(config_dir)
23
23
  FileUtils.mkdir_p(scripts_dir) unless File.directory?(scripts_dir)
24
- File.open(config_file, 'w') { |f| f.puts sample_config }
24
+ File.open(config_file, "w") { |f| f.puts sample_config }
25
25
  puts "Sample config created at #{config_file}"
26
26
 
27
27
  Process.exit 0
data/lib/conductor/env.rb CHANGED
@@ -3,52 +3,54 @@
3
3
  module Conductor
4
4
  module Env
5
5
  def self.env
6
- @env ||= if ENV['CONDUCTOR_TEST'] == 'true'
7
- load_test_env
8
- else
9
- @env ||= {
10
- home: ENV['HOME'],
11
- css_path: ENV['MARKED_CSS_PATH'],
12
- ext: ENV['MARKED_EXT'],
13
- includes: ENV['MARKED_INCLUDES'],
14
- origin: ENV['MARKED_ORIGIN'],
15
- filepath: ENV['MARKED_PATH'],
16
- phase: ENV['MARKED_PHASE'],
17
- outline: ENV['OUTLINE'],
18
- path: ENV['PATH']
19
- }
20
- end
6
+ @env ||= if ENV["CONDUCTOR_TEST"] == "true"
7
+ load_test_env
8
+ else
9
+ @env ||= {
10
+ home: ENV["HOME"],
11
+ css_path: ENV["MARKED_CSS_PATH"],
12
+ ext: ENV["MARKED_EXT"],
13
+ includes: ENV["MARKED_INCLUDES"],
14
+ origin: ENV["MARKED_ORIGIN"],
15
+ filepath: ENV["MARKED_PATH"],
16
+ filename: File.basename(filepath),
17
+ phase: ENV["MARKED_PHASE"],
18
+ outline: ENV["OUTLINE"],
19
+ path: ENV["PATH"]
20
+ }
21
+ end
21
22
 
22
23
  @env
23
24
  end
24
25
 
25
26
  def self.load_test_env
26
27
  @env = {
27
- home: '/Users/ttscoff',
28
- css_path: '/Applications/Marked 2.app/Contents/Resources/swiss.css',
29
- ext: 'md',
28
+ home: "/Users/ttscoff",
29
+ css_path: "/Applications/Marked 2.app/Contents/Resources/swiss.css",
30
+ ext: "md",
30
31
  includes: [],
31
- origin: '/Users/ttscoff/Desktop/Code/marked-conductor/',
32
- filepath: '/Users/ttscoff/Desktop/Code/marked-conductor/README.md',
33
- phase: 'PREPROCESS',
34
- outline: 'NONE',
35
- path: '/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/Users/ttscoff/Dropbox/Writing/brettterpstra.com/_drafts/'
32
+ origin: "/Users/ttscoff/Desktop/Code/marked-conductor/",
33
+ filepath: "/Users/ttscoff/Desktop/Code/marked-conductor/README.md",
34
+ filename: "README.md",
35
+ phase: "PREPROCESS",
36
+ outline: "NONE",
37
+ path: "/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/Users/ttscoff/Dropbox/Writing/brettterpstra.com/_drafts/"
36
38
  }
37
39
  end
38
40
 
39
41
  def self.to_s
40
42
  out_h = {
41
- 'HOME' => @env[:home],
42
- 'MARKED_CSS_PATH' => @env[:css_path],
43
- 'MARKED_EXT' => @env[:ext],
44
- 'MARKED_ORIGIN' => @env[:origin],
45
- 'MARKED_INCLUDES' => @env[:includes],
46
- 'MARKED_PATH' => @env[:filepath],
47
- 'MARKED_PHASE' => @env[:phase],
48
- 'OUTLINE' => @env[:outline],
49
- 'PATH'=> @env[:path]
43
+ "HOME" => @env[:home],
44
+ "MARKED_CSS_PATH" => @env[:css_path],
45
+ "MARKED_EXT" => @env[:ext],
46
+ "MARKED_ORIGIN" => @env[:origin],
47
+ "MARKED_INCLUDES" => @env[:includes],
48
+ "MARKED_PATH" => @env[:filepath],
49
+ "MARKED_PHASE" => @env[:phase],
50
+ "OUTLINE" => @env[:outline],
51
+ "PATH" => @env[:path]
50
52
  }
51
- out_h.map { |k, v| %(#{k}="#{v}") }.join(' ')
53
+ out_h.map { |k, v| %(#{k}="#{v}") }.join(" ")
52
54
  end
53
55
  end
54
56
  end
@@ -6,6 +6,6 @@ class ::Hash
6
6
  end
7
7
 
8
8
  def symbolize_keys
9
- each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
9
+ each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = (v.is_a?(Hash) || v.is_a?(Array)) ? v.symbolize_keys : v }
10
10
  end
11
11
  end
@@ -8,44 +8,44 @@ module Conductor
8
8
  def initialize(script)
9
9
  parts = Shellwords.split(script)
10
10
  self.path = parts[0]
11
- self.args = parts[1..].join(' ')
11
+ self.args = parts[1..].join(" ")
12
12
  end
13
13
 
14
14
  def path=(path)
15
- @path = if path =~ %r{^[%/]}
16
- File.expand_path(path)
17
- else
18
- script_dir = File.expand_path('~/.config/conductor/scripts')
19
- if File.exist?(File.join(script_dir, path))
20
- File.join(script_dir, path)
21
- elsif TTY::Which.exist?(path)
22
- TTY::Which.which(path)
23
- else
24
- raise "Path to #{path} not found"
25
-
26
- end
27
- end
15
+ @path = if %r{^[%/]}.match?(path)
16
+ File.expand_path(path)
17
+ else
18
+ script_dir = File.expand_path("~/.config/conductor/scripts")
19
+ if File.exist?(File.join(script_dir, path))
20
+ File.join(script_dir, path)
21
+ elsif TTY::Which.exist?(path)
22
+ TTY::Which.which(path)
23
+ else
24
+ raise "Path to #{path} not found"
25
+
26
+ end
27
+ end
28
28
  end
29
29
 
30
30
  def args=(array)
31
31
  @args = if array.is_a?(Array)
32
- array.join(' ')
33
- else
34
- array
35
- end
32
+ array.join(" ")
33
+ else
34
+ array
35
+ end
36
36
  end
37
37
 
38
38
  def run
39
39
  stdin = Conductor.stdin
40
40
 
41
- raise 'Script path not defined' unless @path
41
+ raise "Script path not defined" unless @path
42
42
 
43
43
  use_stdin = true
44
- if args =~ /\$\{?file\}?/
44
+ if /\$\{?file\}?/.match?(args)
45
45
  use_stdin = false
46
46
  args.sub!(/\$\{?file\}?/, Env.env[:filepath])
47
47
  else
48
- raise 'No input' unless stdin
48
+ raise "No input" unless stdin
49
49
 
50
50
  end
51
51
 
@@ -4,9 +4,9 @@
4
4
  class ::String
5
5
  def bool_to_symbol
6
6
  case self
7
- when /NOT/
7
+ when /(NOT|!!)/
8
8
  :not
9
- when /AND/
9
+ when /(AND|&&)/
10
10
  :and
11
11
  else
12
12
  :or
@@ -14,32 +14,32 @@ class ::String
14
14
  end
15
15
 
16
16
  def date?
17
- dup.force_encoding('utf-8').match(/^\d{4}-\d{2}-\d{2}/) ? true : false
17
+ dup.force_encoding("utf-8").match?(/^\d{4}-\d{2}-\d{2}/)
18
18
  end
19
19
 
20
20
  def time?
21
- dup.force_encoding('utf-8').match(/ \d{1,2}(:\d\d)? *([ap]m)?/i)
21
+ dup.force_encoding("utf-8").match(/ \d{1,2}(:\d\d)? *([ap]m)?/i)
22
22
  end
23
23
 
24
24
  def to_date
25
- Chronic.parse(self.dup.force_encoding('utf-8'))
25
+ Chronic.parse(dup.force_encoding("utf-8"))
26
26
  end
27
27
 
28
28
  def strip_time
29
- dup.force_encoding('utf-8').sub(/ \d{1,2}(:\d\d)? *([ap]m)?/i, '')
29
+ dup.force_encoding("utf-8").sub(/ \d{1,2}(:\d\d)? *([ap]m)?/i, "")
30
30
  end
31
31
 
32
32
  def to_day(time = :end)
33
- t = time == :end ? '23:59' : '00:00'
34
- Chronic.parse("#{self.strip_time} #{t}")
33
+ t = time == :end ? "23:59" : "00:00"
34
+ Chronic.parse("#{strip_time} #{t}")
35
35
  end
36
36
 
37
37
  def number?
38
- to_f > 0
38
+ to_f.positive?
39
39
  end
40
40
 
41
41
  def bool?
42
- dup.force_encoding('utf-8').match(/^(?:y(?:es)?|no?|t(?:rue)?|f(?:alse)?)$/) ? true : false
42
+ dup.force_encoding("utf-8").match?(/^(?:y(?:es)?|no?|t(?:rue)?|f(?:alse)?)$/)
43
43
  end
44
44
 
45
45
  def meta?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Conductor
4
- VERSION = '1.0.7'
4
+ VERSION = "1.0.8"
5
5
  end
data/lib/conductor.rb CHANGED
@@ -1,28 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tty-which'
4
- require 'yaml'
5
- require 'shellwords'
6
- require 'fcntl'
7
- require 'time'
8
- require 'chronic'
9
- require 'fileutils'
10
- require_relative 'conductor/version'
11
- require_relative 'conductor/env'
12
- require_relative 'conductor/config'
13
- require_relative 'conductor/hash'
14
- require_relative 'conductor/array'
15
- require_relative 'conductor/boolean'
16
- require_relative 'conductor/string'
17
- require_relative 'conductor/script'
18
- require_relative 'conductor/command'
19
- require_relative 'conductor/condition'
3
+ require "tty-which"
4
+ require "yaml"
5
+ require "shellwords"
6
+ require "fcntl"
7
+ require "time"
8
+ require "chronic"
9
+ require "fileutils"
10
+ require_relative "conductor/version"
11
+ require_relative "conductor/env"
12
+ require_relative "conductor/config"
13
+ require_relative "conductor/hash"
14
+ require_relative "conductor/array"
15
+ require_relative "conductor/boolean"
16
+ require_relative "conductor/string"
17
+ require_relative "conductor/script"
18
+ require_relative "conductor/command"
19
+ require_relative "conductor/condition"
20
20
 
21
21
  module Conductor
22
22
  class << self
23
+ attr_writer :stdin
24
+
23
25
  def stdin
24
- warn 'input on STDIN required' unless $stdin.stat.size.positive? || $stdin.fcntl(Fcntl::F_GETFL, 0).zero?
25
- @stdin ||= $stdin.read.strip.force_encoding('utf-8')
26
+ warn "input on STDIN required" unless $stdin.stat.size.positive? || $stdin.fcntl(Fcntl::F_GETFL, 0).zero?
27
+ @stdin ||= $stdin.read.strip.force_encoding("utf-8")
26
28
  end
27
29
  end
28
30
  end
@@ -3,15 +3,15 @@
3
3
  require_relative "lib/conductor/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "marked-conductor"
7
- spec.version = Conductor::VERSION
8
- spec.authors = ["Brett Terpstra"]
9
- spec.email = ["me@brettterpstra.com"]
6
+ spec.name = "marked-conductor"
7
+ spec.version = Conductor::VERSION
8
+ spec.authors = ["Brett Terpstra"]
9
+ spec.email = ["me@brettterpstra.com"]
10
10
 
11
- spec.summary = "A custom processor manager for Marked 2 (Mac)"
12
- spec.description = "Conductor allows easy configuration of multiple scripts that are run as custom pre/processors for Marked based on conditional statements."
13
- spec.homepage = "https://github.com/ttscoff/marked-conductor"
14
- spec.license = "MIT"
11
+ spec.summary = "A custom processor manager for Marked 2 (Mac)"
12
+ spec.description = "Conductor allows easy configuration of multiple scripts that are run as custom pre/processors for Marked based on conditional statements."
13
+ spec.homepage = "https://github.com/ttscoff/marked-conductor"
14
+ spec.license = "MIT"
15
15
  spec.required_ruby_version = ">= 2.6.0"
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
@@ -25,12 +25,20 @@ Gem::Specification.new do |spec|
25
25
  (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
26
  end
27
27
  end
28
- spec.bindir = "bin"
29
- spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
28
+ spec.bindir = "bin"
29
+ spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
32
  spec.add_development_dependency "pry", "~> 0.14.2"
33
33
  spec.add_development_dependency "awesome_print", "~> 1.9.2"
34
+ spec.add_development_dependency "bundler", "~> 2.0"
35
+ spec.add_development_dependency "gem-release", "~> 2.2"
36
+ spec.add_development_dependency "parse_gemspec-cli", "~> 1.0"
37
+ spec.add_development_dependency "rake", "~> 13.0"
38
+ spec.add_development_dependency "yard", "~> 0.9", ">= 0.9.26"
39
+ spec.add_development_dependency "rspec", "~> 3.0"
40
+ spec.add_development_dependency "simplecov", "~> 0.21"
41
+ spec.add_development_dependency "simplecov-console", "~> 0.9"
34
42
 
35
43
  # Uncomment to register a new dependency of your gem
36
44
  spec.add_dependency "tty-which", "~> 0.5.0"
data/src/_README.md CHANGED
@@ -64,6 +64,26 @@ tracks:
64
64
  command: obsidian-md-filter
65
65
  ```
66
66
 
67
+ #### Adding a title
68
+
69
+ Tracks can contain a `title` key. This is only used in the STDERR output of the track, where 'Met condition: ...' is shown for debugging. If a title is not present, the condition itself will be shown for debugging. If a title is defined, it replaces the condition in the STDERR output. This is mostly for shortening long condition strings to something more meaningful for debugging.
70
+
71
+ ### Sequencing
72
+
73
+ A track can also contain a sequence of scripts and/or commands. STDIN will be passed into the first script/command, then the STDOUT of that will be piped to the next script/command. To do this, add a key called `sequence` that contains an array of scripts and commands:
74
+
75
+ ```yaml
76
+ tracks:
77
+ - condition: phase is pro AND path contains README.md
78
+ sequence:
79
+ - script: strip_emoji
80
+ - command: rdiscount
81
+ ```
82
+
83
+ A sequence can not contain nested tracks.
84
+
85
+ By default, processing stops when a condition is met. If you want to continue processing after a condition is successful, add the `continue: true` to the track. This will only apply to tracks containing this key, and processing will stop when it gets to a successful condition that doesn't contain the `continue` key (or reaches the end of the tracks without another match).
86
+
67
87
  ### Conditions
68
88
 
69
89
  Available conditions are:
@@ -71,6 +91,7 @@ Available conditions are:
71
91
  - `extension` (or `ext`): This will test the extension of the file, e.g. `ext is md` or `ext contains task`
72
92
  - `tree contains ...`: This will test whether a given file or directory exists in any of the parent folders of the current file, starting with the current directory of the file. Example: `tree contains .obsidian` would test whether there was an `.obsidian` directory in any of the directories above the file (indicating it's within an Obsidian vault)
73
93
  - `path`: This tests just the path to the file itself, allowing conditions like `path contains _drafts` or `path does not contain _posts`.
94
+ - `filename`: Tests only the filename, can be any string comparison (`starts with`, `is`, `contains`, etc.).
74
95
  - `phase`: Tests whether Marked is in Preprocessor or Processor phase, allowing conditions like `phase is preprocess` or `phase is process` (which can be shortened to `pre` and `pro`).
75
96
  - `text`: This tests for any string match within the text of the document being processed. This can be used with operators `starts with`, `ends with`, or `contains`, e.g. `text contains @taskpaper` or `text does not contain <!--more-->`.
76
97
  - If the test value is surrounded by forward slashes, it will be treated as a regular expression. Regexes are always flagged as case insensitive. Use it like `text contains /@\w+/`.
@@ -82,7 +103,7 @@ Available conditions are:
82
103
  - If the YAML key is a date, it can be tested against with `before`, `after`, and `is`, and the value can be a natural language date, e.g. `yaml:date is after may 3, 2024`
83
104
  - If both the YAML key value and the test value are numbers, you can use operators `greater than` (`>`), `less than` (`<`), `equal`/`is` (`=`/`==`), and `is not equal`/`not equals` (`!=`/`!==`). Numbers will be interpreted as floats.
84
105
  - If the YAML value is a boolean, you can test with `is true` or `is not true` (or `is false`)
85
- - `mmd` or `meta` will test for MultiMarkdown metadata using the same formatting as `yaml`.
106
+ - `mmd` or `meta` will test for MultiMarkdown metadata using the same formatting as `yaml` above.
86
107
  - The following keywords act as a catchall and can be used as the last track in the config to act on any documents that aren't matched by preceding rules:
87
108
  - `any`
88
109
  - `else`
@@ -115,6 +136,9 @@ All of the [capabilities and requirements](https://marked2app.com/help/Custom_Pr
115
136
 
116
137
  A script run by Conductor already knows it has the right type of file with the expected data and path, so your script can focus on just processing one file type. It's recommended to separate all of that logic you may already have written out into separate scripts and let Conductor handle the forking based on various criteria.
117
138
 
139
+ > Custom processors **must** wait for input on STDIN. Most markdown CLIs will do this automatically, but scripts should include a call to read STDIN. This will pause the script and wait for the data to be sent. Without this, Marked will launch the script, and if it closes the pipe, it will try to write data to a closed pipe and crash immediately. This is a very difficult error to trap in Marked, so it's crucial that all scripts keep the STDIN pipe open.
140
+ <!--JEKYLL{:.warn}-->
141
+
118
142
  ## Tips
119
143
 
120
144
  - Config file must be valid YAML. Any value containing colons, brackets, or other special characters should be quoted, e.g. (`condition: "text contains my:text"`)
@@ -124,6 +148,12 @@ A script run by Conductor already knows it has the right type of file with the e
124
148
 
125
149
  ## Testing
126
150
 
151
+ You can test conductor setups using Marked's `Help->Show Custom Processor Log`, or by running from the command line. The easiest way to test conditions is to set the track's command to `echo "meaningful definition"` and see what conditions are met when conductor is run.
152
+
153
+ In Marked's Custom Processor Log, you can see both the STDOUT output and the STDERR messages. When running Conductor, the STDERR output will show what conditions were met (as well as any errors reported).
154
+
155
+ ### From the command line
156
+
127
157
  In order to test from the command line, you'll need certain environment variables set. This can be done by exporting the following variables with your own definitions, or by running conductor with all of the variables preceding the command, e.g. `$ MARKED_ORIGIN=/path/to/markdown_file.md [...] conductor`.
128
158
 
129
159
  The following need to be defined. Some can be left as empty or to defaults, such as `MARKED_INCLUDES` and `MARKED_OUTLINE`, but all need to be set to something.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marked-conductor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-26 00:00:00.000000000 Z
11
+ date: 2024-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -38,6 +38,124 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.9.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: gem-release
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: parse_gemspec-cli
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '13.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '13.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 0.9.26
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '0.9'
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 0.9.26
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '3.0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: simplecov
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.21'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '0.21'
145
+ - !ruby/object:Gem::Dependency
146
+ name: simplecov-console
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '0.9'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.9'
41
159
  - !ruby/object:Gem::Dependency
42
160
  name: tty-which
43
161
  requirement: !ruby/object:Gem::Requirement
@@ -83,6 +201,7 @@ files:
83
201
  - Gemfile.lock
84
202
  - LICENSE.txt
85
203
  - README.md
204
+ - README.rdoc
86
205
  - Rakefile
87
206
  - bin/conductor
88
207
  - images/preferences.jpg