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 +4 -4
- data/.irbrc +8 -2
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +47 -3
- data/README.md +31 -1
- data/README.rdoc +6 -0
- data/Rakefile +38 -1
- data/bin/conductor +44 -15
- data/lib/conductor/array.rb +0 -1
- data/lib/conductor/command.rb +14 -14
- data/lib/conductor/condition.rb +128 -34
- data/lib/conductor/config.rb +4 -4
- data/lib/conductor/env.rb +35 -33
- data/lib/conductor/hash.rb +1 -1
- data/lib/conductor/script.rb +21 -21
- data/lib/conductor/string.rb +10 -10
- data/lib/conductor/version.rb +1 -1
- data/lib/conductor.rb +21 -19
- data/marked-conductor.gemspec +18 -10
- data/src/_README.md +31 -1
- metadata +121 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc01f58de2ca4d81c45420e08128c3ae0dd3f125badce5d62a8cc568c599d202
|
4
|
+
data.tar.gz: ad0724664a346aea50e4467d7a492737d885b6b2916f04077a73866b3ba0e915
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b812adad30a746bc2b38f302047a595796d8ec2886b503a198b1384dd570d0b61d41de1029788c95b3f6a579c98c4571bef01c00c8f866aeb6245028676ab104
|
7
|
+
data.tar.gz: 11f506aa92cdc7ea0c5f219b84d694615c32da729ba57dfafa0f4eb9aa258d33c69c36383d858b0d0764c03362eb7e6db0b3fe4862b441db3d326e5c5f678f36
|
data/.irbrc
CHANGED
data/.rubocop.yml
CHANGED
@@ -3,11 +3,11 @@ AllCops:
|
|
3
3
|
|
4
4
|
Style/StringLiterals:
|
5
5
|
Enabled: true
|
6
|
-
EnforcedStyle:
|
6
|
+
EnforcedStyle: double_quotes
|
7
7
|
|
8
8
|
Style/StringLiteralsInInterpolation:
|
9
9
|
Enabled: true
|
10
|
-
EnforcedStyle:
|
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.
|
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
|
-
|
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
|
-
|
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
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__),
|
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
|
-
|
27
|
-
|
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
|
-
|
56
|
+
res = execute_track(track)
|
30
57
|
|
31
|
-
if track[:
|
32
|
-
|
58
|
+
if track[:tracks]
|
59
|
+
ts = track[:tracks]
|
33
60
|
|
34
|
-
res =
|
35
|
-
elsif track[:command]
|
36
|
-
command = Command.new(track[:command])
|
61
|
+
res, condition = conduct(ts, res, condition)
|
37
62
|
|
38
|
-
|
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
|
|
data/lib/conductor/array.rb
CHANGED
data/lib/conductor/command.rb
CHANGED
@@ -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
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
34
|
+
raise "Command path not found" unless @path
|
35
35
|
|
36
36
|
use_stdin = true
|
37
|
-
if
|
37
|
+
if /\$\{?file\}?/.match?(args)
|
38
38
|
use_stdin = false
|
39
39
|
args.sub!(/\$\{?file\}?/, %("#{Env.env[:filepath]}"))
|
40
40
|
else
|
41
|
-
raise
|
41
|
+
raise "No input" unless stdin
|
42
42
|
|
43
43
|
end
|
44
44
|
|
data/lib/conductor/condition.rb
CHANGED
@@ -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
|
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[
|
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(
|
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
|
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(
|
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,
|
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
|
246
|
+
return operator == :not_equal unless yaml
|
166
247
|
|
167
248
|
if key
|
168
249
|
value1 = yaml[key]
|
169
|
-
return operator == :not_equal
|
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
|
-
|
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? &&
|
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(
|
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(
|
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(
|
238
|
-
when /^(yaml|headers|frontmatter)(?::(.*?))?$/i
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
data/lib/conductor/config.rb
CHANGED
@@ -6,22 +6,22 @@ module Conductor
|
|
6
6
|
attr_reader :config, :tracks
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
config_file = File.expand_path(
|
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[
|
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,
|
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,
|
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[
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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:
|
28
|
-
css_path:
|
29
|
-
ext:
|
28
|
+
home: "/Users/ttscoff",
|
29
|
+
css_path: "/Applications/Marked 2.app/Contents/Resources/swiss.css",
|
30
|
+
ext: "md",
|
30
31
|
includes: [],
|
31
|
-
origin:
|
32
|
-
filepath:
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
data/lib/conductor/hash.rb
CHANGED
data/lib/conductor/script.rb
CHANGED
@@ -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
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
41
|
+
raise "Script path not defined" unless @path
|
42
42
|
|
43
43
|
use_stdin = true
|
44
|
-
if
|
44
|
+
if /\$\{?file\}?/.match?(args)
|
45
45
|
use_stdin = false
|
46
46
|
args.sub!(/\$\{?file\}?/, Env.env[:filepath])
|
47
47
|
else
|
48
|
-
raise
|
48
|
+
raise "No input" unless stdin
|
49
49
|
|
50
50
|
end
|
51
51
|
|
data/lib/conductor/string.rb
CHANGED
@@ -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(
|
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(
|
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(
|
25
|
+
Chronic.parse(dup.force_encoding("utf-8"))
|
26
26
|
end
|
27
27
|
|
28
28
|
def strip_time
|
29
|
-
dup.force_encoding(
|
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 ?
|
34
|
-
Chronic.parse("#{
|
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
|
38
|
+
to_f.positive?
|
39
39
|
end
|
40
40
|
|
41
41
|
def bool?
|
42
|
-
dup.force_encoding(
|
42
|
+
dup.force_encoding("utf-8").match?(/^(?:y(?:es)?|no?|t(?:rue)?|f(?:alse)?)$/)
|
43
43
|
end
|
44
44
|
|
45
45
|
def meta?
|
data/lib/conductor/version.rb
CHANGED
data/lib/conductor.rb
CHANGED
@@ -1,28 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require_relative
|
11
|
-
require_relative
|
12
|
-
require_relative
|
13
|
-
require_relative
|
14
|
-
require_relative
|
15
|
-
require_relative
|
16
|
-
require_relative
|
17
|
-
require_relative
|
18
|
-
require_relative
|
19
|
-
require_relative
|
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
|
25
|
-
@stdin ||= $stdin.read.strip.force_encoding(
|
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
|
data/marked-conductor.gemspec
CHANGED
@@ -3,15 +3,15 @@
|
|
3
3
|
require_relative "lib/conductor/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
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
|
12
|
-
spec.description
|
13
|
-
spec.homepage
|
14
|
-
spec.license
|
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
|
29
|
-
spec.executables
|
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.
|
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-
|
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
|