ukiryu 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/ukiryu/cache.rb +6 -0
- data/lib/ukiryu/cache_registry.rb +64 -0
- data/lib/ukiryu/cli_commands/base_command.rb +6 -5
- data/lib/ukiryu/cli_commands/config_command.rb +7 -10
- data/lib/ukiryu/cli_commands/register_command.rb +27 -18
- data/lib/ukiryu/cli_commands/validate_command.rb +2 -2
- data/lib/ukiryu/command_builder.rb +83 -50
- data/lib/ukiryu/config.rb +13 -2
- data/lib/ukiryu/debug.rb +20 -9
- data/lib/ukiryu/definition/loader.rb +3 -3
- data/lib/ukiryu/errors.rb +37 -37
- data/lib/ukiryu/executable_locator.rb +40 -16
- data/lib/ukiryu/extractors/base_extractor.rb +2 -1
- data/lib/ukiryu/extractors/help_parser.rb +3 -0
- data/lib/ukiryu/logger.rb +51 -0
- data/lib/ukiryu/models/implementation_index.rb +2 -1
- data/lib/ukiryu/models/implementation_version.rb +18 -1
- data/lib/ukiryu/models/interface.rb +2 -1
- data/lib/ukiryu/models/run_environment.rb +0 -2
- data/lib/ukiryu/models/semantic_version.rb +174 -0
- data/lib/ukiryu/models/stage_metrics.rb +0 -1
- data/lib/ukiryu/register.rb +473 -232
- data/lib/ukiryu/shell/powershell.rb +209 -89
- data/lib/ukiryu/shell/sh.rb +4 -1
- data/lib/ukiryu/shell.rb +60 -2
- data/lib/ukiryu/tool/command_resolution.rb +2 -1
- data/lib/ukiryu/tool/executable_discovery.rb +14 -15
- data/lib/ukiryu/tool/loader.rb +543 -0
- data/lib/ukiryu/tool/version_detection.rb +1 -3
- data/lib/ukiryu/tool.rb +79 -87
- data/lib/ukiryu/tool_index.rb +127 -62
- data/lib/ukiryu/tools/base.rb +4 -2
- data/lib/ukiryu/type.rb +26 -15
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu.rb +1 -1
- data/spec/fixtures/profiles/ghostscript_10.0.yaml +50 -0
- data/spec/fixtures/register/tools/ghostscript/default/10.0.yaml +6 -0
- data/spec/spec_helper.rb +10 -6
- data/spec/support/tool_helper.rb +2 -0
- data/spec/ukiryu/definition/loader_spec.rb +2 -2
- data/spec/ukiryu/executor_spec.rb +6 -3
- data/spec/ukiryu/models/execution_report_spec.rb +3 -2
- data/spec/ukiryu/models/semantic_version_spec.rb +284 -0
- data/spec/ukiryu/shell/powershell_integration_spec.rb +165 -0
- data/spec/ukiryu/shell/powershell_real_command_spec.rb +143 -0
- data/spec/ukiryu/shell/powershell_spec.rb +286 -51
- data/spec/ukiryu/tool/loader_spec.rb +148 -0
- data/spec/ukiryu/tool_index_spec.rb +110 -18
- data/spec/ukiryu/tools/ghostscript_spec.rb +242 -0
- data/spec/ukiryu/tools/imagemagick_spec.rb +2 -1
- data/spec/ukiryu/tools/inkscape_spec.rb +4 -2
- metadata +14 -2
- data/lib/ukiryu/register_auto_manager.rb +0 -342
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe 'PowerShell Integration Tests', if: system('which pwsh > /dev/null 2>&1') do
|
|
6
|
+
let(:shell) { Ukiryu::Shell::PowerShell.new }
|
|
7
|
+
let(:env) { Ukiryu::Environment.system }
|
|
8
|
+
|
|
9
|
+
describe '#execute_command' do
|
|
10
|
+
context 'with dash-prefixed arguments (prefix stripping prevention)' do
|
|
11
|
+
it 'preserves -sDEVICE=pdfwrite style arguments' do
|
|
12
|
+
result = shell.execute_command('echo', ['-sDEVICE=pdfwrite', 'input.eps'], env, 30, nil)
|
|
13
|
+
expect(result[:status]).to eq(0)
|
|
14
|
+
# The full argument should be present (not stripped to just =pdfwrite)
|
|
15
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'preserves multiple dash-prefixed arguments' do
|
|
19
|
+
args = ['-sDEVICE=pdfwrite', '-sOutputFile=output.pdf', '-dBATCH']
|
|
20
|
+
result = shell.execute_command('echo', args, env, 30, nil)
|
|
21
|
+
expect(result[:status]).to eq(0)
|
|
22
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
23
|
+
expect(result[:stdout]).to include('-sOutputFile=output.pdf')
|
|
24
|
+
expect(result[:stdout]).to include('-dBATCH')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'preserves -resize style arguments' do
|
|
28
|
+
result = shell.execute_command('echo', ['-resize', '50x50'], env, 30, nil)
|
|
29
|
+
expect(result[:status]).to eq(0)
|
|
30
|
+
expect(result[:stdout]).to include('-resize')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'with single quotes in arguments' do
|
|
35
|
+
it 'correctly escapes single quotes by doubling them' do
|
|
36
|
+
result = shell.execute_command('echo', ["it's a test"], env, 30, nil)
|
|
37
|
+
expect(result[:status]).to eq(0)
|
|
38
|
+
expect(result[:stdout].strip).to eq("it's a test")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'handles multiple single quotes' do
|
|
42
|
+
result = shell.execute_command('echo', ["it's a test's value"], env, 30, nil)
|
|
43
|
+
expect(result[:status]).to eq(0)
|
|
44
|
+
expect(result[:stdout].strip).to eq("it's a test's value")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'with special characters in arguments' do
|
|
49
|
+
it 'handles dollar signs (escaped in double quotes)' do
|
|
50
|
+
result = shell.execute_command('echo', ['$VAR'], env, 30, nil)
|
|
51
|
+
expect(result[:status]).to eq(0)
|
|
52
|
+
# Dollar signs are escaped with backtick in double-quoted strings
|
|
53
|
+
expect(result[:stdout].strip).to eq('$VAR')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'handles backticks (escaped in double quotes)' do
|
|
57
|
+
result = shell.execute_command('echo', ['`hello`'], env, 30, nil)
|
|
58
|
+
expect(result[:status]).to eq(0)
|
|
59
|
+
# Backticks are escaped with backtick in double-quoted strings
|
|
60
|
+
expect(result[:stdout].strip).to eq('`hello`')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'handles arguments with spaces' do
|
|
64
|
+
result = shell.execute_command('echo', ['hello world'], env, 30, nil)
|
|
65
|
+
expect(result[:status]).to eq(0)
|
|
66
|
+
expect(result[:stdout].strip).to eq('hello world')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'handles semicolons safely' do
|
|
70
|
+
result = shell.execute_command('echo', ['hello;world'], env, 30, nil)
|
|
71
|
+
expect(result[:status]).to eq(0)
|
|
72
|
+
expect(result[:stdout].strip).to eq('hello;world')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'with executable paths' do
|
|
77
|
+
it 'handles paths with spaces' do
|
|
78
|
+
# Use /bin/echo which is a simple command that exists
|
|
79
|
+
result = shell.execute_command('/bin/echo', ['test'], env, 30, nil)
|
|
80
|
+
expect(result[:status]).to eq(0)
|
|
81
|
+
expect(result[:stdout].strip).to eq('test')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe '#join' do
|
|
87
|
+
context 'with dash-prefixed arguments' do
|
|
88
|
+
it 'quotes arguments starting with dash with double quotes' do
|
|
89
|
+
cmd = shell.join('gs', '-sDEVICE=pdfwrite', 'input.eps', 'output.pdf')
|
|
90
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'quotes multiple dash-prefixed arguments' do
|
|
94
|
+
cmd = shell.join('gs', '-sDEVICE=pdfwrite', '-dBATCH', 'input.eps')
|
|
95
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
96
|
+
expect(cmd).to include('"-dBATCH"')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'with arguments containing special characters' do
|
|
101
|
+
it 'quotes and escapes arguments with dollar signs' do
|
|
102
|
+
cmd = shell.join('echo', '$VAR')
|
|
103
|
+
# Dollar sign is escaped with backtick in double-quoted strings
|
|
104
|
+
expect(cmd).to include('"`$VAR"')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'quotes arguments with spaces' do
|
|
108
|
+
cmd = shell.join('echo', 'hello world')
|
|
109
|
+
expect(cmd).to include('"hello world"')
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'with simple arguments' do
|
|
114
|
+
it 'does not quote simple arguments' do
|
|
115
|
+
cmd = shell.join('echo', 'hello', 'world')
|
|
116
|
+
expect(cmd).to eq('echo hello world')
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context 'with executable paths containing spaces' do
|
|
121
|
+
it 'quotes the executable path' do
|
|
122
|
+
cmd = shell.join('/path with space/gs', '-sDEVICE=pdfwrite')
|
|
123
|
+
expect(cmd).to include('"/path with space/gs"')
|
|
124
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe '#execute_command_with_stdin' do
|
|
130
|
+
it 'passes stdin data correctly' do
|
|
131
|
+
result = shell.execute_command_with_stdin('cat', [], env, 30, nil, 'hello from stdin')
|
|
132
|
+
expect(result[:status]).to eq(0)
|
|
133
|
+
expect(result[:stdout].strip).to eq('hello from stdin')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'preserves dash-prefixed arguments with stdin' do
|
|
137
|
+
# Use /bin/echo instead of 'echo' because PowerShell's echo alias (Write-Output)
|
|
138
|
+
# doesn't handle stdin piping correctly on Unix
|
|
139
|
+
result = shell.execute_command_with_stdin('/bin/echo', ['-sDEVICE=pdfwrite'], env, 30, nil, '')
|
|
140
|
+
expect(result[:status]).to eq(0)
|
|
141
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe 'real-world Ghostscript command simulation' do
|
|
146
|
+
it 'formats Ghostscript-style command correctly' do
|
|
147
|
+
# Simulate the command Vectory would run
|
|
148
|
+
args = [
|
|
149
|
+
'-sDEVICE=pdfwrite',
|
|
150
|
+
'-sOutputFile=output.pdf',
|
|
151
|
+
'-dBATCH',
|
|
152
|
+
'-dNOPAUSE',
|
|
153
|
+
'input.ps'
|
|
154
|
+
]
|
|
155
|
+
result = shell.execute_command('echo', args, env, 30, nil)
|
|
156
|
+
|
|
157
|
+
expect(result[:status]).to eq(0)
|
|
158
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
159
|
+
expect(result[:stdout]).to include('-sOutputFile=output.pdf')
|
|
160
|
+
expect(result[:stdout]).to include('-dBATCH')
|
|
161
|
+
expect(result[:stdout]).to include('-dNOPAUSE')
|
|
162
|
+
expect(result[:stdout]).to include('input.ps')
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe 'PowerShell Real Command Integration', if: system('which pwsh > /dev/null 2>&1') do
|
|
6
|
+
let(:shell) { Ukiryu::Shell::PowerShell.new }
|
|
7
|
+
let(:env) { Ukiryu::Environment.system }
|
|
8
|
+
|
|
9
|
+
describe 'End-to-end command execution with Ghostscript-style args' do
|
|
10
|
+
it 'correctly passes -sDEVICE=pdfwrite style arguments' do
|
|
11
|
+
# This is the exact pattern Vectory uses with Ghostscript
|
|
12
|
+
args = [
|
|
13
|
+
'-sDEVICE=pdfwrite',
|
|
14
|
+
'-sOutputFile=output.pdf',
|
|
15
|
+
'-dBATCH',
|
|
16
|
+
'-dNOPAUSE',
|
|
17
|
+
'input.eps'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
result = shell.execute_command('echo', args, env, 30, nil)
|
|
21
|
+
|
|
22
|
+
expect(result[:status]).to eq(0)
|
|
23
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
24
|
+
expect(result[:stdout]).to include('-sOutputFile=output.pdf')
|
|
25
|
+
expect(result[:stdout]).to include('-dBATCH')
|
|
26
|
+
expect(result[:stdout]).to include('-dNOPAUSE')
|
|
27
|
+
expect(result[:stdout]).to include('input.eps')
|
|
28
|
+
|
|
29
|
+
# Ensure the full arguments are present (prefix not stripped)
|
|
30
|
+
expect(result[:stdout]).to match(/-sDEVICE=pdfwrite/)
|
|
31
|
+
expect(result[:stdout]).to match(/-sOutputFile=output.pdf/)
|
|
32
|
+
|
|
33
|
+
# Ensure we don't see arguments that start with = (prefix stripped)
|
|
34
|
+
# This would indicate the -sDEVICE prefix was stripped
|
|
35
|
+
expect(result[:stdout]).not_to match(/\s=pdfwrite\s/)
|
|
36
|
+
expect(result[:stdout]).not_to match(/\s=output\.pdf\s/)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'handles multiple consecutive dash-prefixed args' do
|
|
40
|
+
args = ['-a', '-b', '-c=value', '-d:value', 'file.txt']
|
|
41
|
+
result = shell.execute_command('echo', args, env, 30, nil)
|
|
42
|
+
|
|
43
|
+
expect(result[:status]).to eq(0)
|
|
44
|
+
expect(result[:stdout]).to include('-a')
|
|
45
|
+
expect(result[:stdout]).to include('-b')
|
|
46
|
+
expect(result[:stdout]).to include('-c=value')
|
|
47
|
+
expect(result[:stdout]).to include('-d:value')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'handles dash-prefixed args with spaces in values' do
|
|
51
|
+
args = ['-sOutputFile=C:/Program Files/output.pdf', 'input.eps']
|
|
52
|
+
result = shell.execute_command('echo', args, env, 30, nil)
|
|
53
|
+
|
|
54
|
+
expect(result[:status]).to eq(0)
|
|
55
|
+
expect(result[:stdout]).to include('-sOutputFile=C:/Program Files/output.pdf')
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe 'Command line building via join()' do
|
|
60
|
+
it 'produces correct command line for Ghostscript-style args' do
|
|
61
|
+
cmd = shell.join(
|
|
62
|
+
'C:/Program Files/gs/gs10.00.0/bin/gswin64c.exe',
|
|
63
|
+
'-sDEVICE=pdfwrite',
|
|
64
|
+
'-sOutputFile=output.pdf',
|
|
65
|
+
'-dBATCH',
|
|
66
|
+
'input.eps'
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# All dash-prefixed args should be quoted
|
|
70
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
71
|
+
expect(cmd).to include('"-sOutputFile=output.pdf"')
|
|
72
|
+
expect(cmd).to include('"-dBATCH"')
|
|
73
|
+
|
|
74
|
+
# Simple arg should not be quoted
|
|
75
|
+
expect(cmd).to include(' input.eps')
|
|
76
|
+
|
|
77
|
+
# Should NOT contain stripped prefixes (standalone = without the - prefix)
|
|
78
|
+
expect(cmd).not_to match(/\s=pdfwrite\b/)
|
|
79
|
+
expect(cmd).not_to match(/\s=output\.pdf\b/)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'handles executable paths with spaces' do
|
|
83
|
+
cmd = shell.join('C:/Program Files/gs/gswin64c.exe', '-sDEVICE=pdfwrite')
|
|
84
|
+
|
|
85
|
+
# Executable path should be quoted
|
|
86
|
+
expect(cmd).to include('"C:/Program Files/gs/gswin64c.exe"')
|
|
87
|
+
|
|
88
|
+
# Dash-prefixed arg should be quoted
|
|
89
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe 'Tool integration with Ghostscript profile' do
|
|
94
|
+
let(:ghostscript_profile) { 'spec/fixtures/profiles/ghostscript_10.0.yaml' }
|
|
95
|
+
|
|
96
|
+
it 'builds correct args through Tool#build_args' do
|
|
97
|
+
skip 'Ghostscript profile not found' unless File.exist?(ghostscript_profile)
|
|
98
|
+
|
|
99
|
+
tool = Ukiryu::Tool.from_file(ghostscript_profile, platform: :windows, shell: :powershell)
|
|
100
|
+
|
|
101
|
+
command = tool.command_definition(:convert)
|
|
102
|
+
params = { device: 'pdfwrite', output: 'output.pdf', inputs: ['input.eps'] }
|
|
103
|
+
|
|
104
|
+
args = tool.build_args(command, params)
|
|
105
|
+
|
|
106
|
+
# Verify args are correct
|
|
107
|
+
expect(args).to be_an(Array)
|
|
108
|
+
expect(args).to include('-sDEVICE=pdfwrite')
|
|
109
|
+
expect(args).to include('-sOutputFile=output.pdf')
|
|
110
|
+
|
|
111
|
+
# Each arg should be a String, not an Array
|
|
112
|
+
args.each do |arg|
|
|
113
|
+
expect(arg).to be_a(String), "Expected String, got #{arg.class}: #{arg.inspect}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe 'Edge cases that could cause prefix stripping' do
|
|
119
|
+
it 'handles args passed as nested arrays (should be flattened)' do
|
|
120
|
+
# This simulates what might happen if someone passes args incorrectly
|
|
121
|
+
nested_args = [['-sDEVICE=pdfwrite', 'input.eps']]
|
|
122
|
+
|
|
123
|
+
# When nested arrays are passed to join, they get stringified
|
|
124
|
+
# This is the problematic behavior we need to detect
|
|
125
|
+
cmd = shell.join('gswin64c.exe', *nested_args)
|
|
126
|
+
|
|
127
|
+
# The nested array becomes a string like '["-sDEVICE=pdfwrite", "input.eps"]'
|
|
128
|
+
# which is NOT what we want
|
|
129
|
+
# This test documents the current behavior - we should NOT have nested arrays
|
|
130
|
+
expect(cmd).to include('[') # Array stringification
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'correctly handles flat args vs nested args' do
|
|
134
|
+
flat_args = ['-sDEVICE=pdfwrite', 'input.eps']
|
|
135
|
+
cmd_flat = shell.join('gswin64c.exe', *flat_args)
|
|
136
|
+
|
|
137
|
+
# Flat args should NOT have array stringification
|
|
138
|
+
expect(cmd_flat).not_to include('[')
|
|
139
|
+
expect(cmd_flat).not_to include(']')
|
|
140
|
+
expect(cmd_flat).to include('"-sDEVICE=pdfwrite"')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|