Skip to content

Commit

Permalink
Resolve input/output filenames to absolute paths by default
Browse files Browse the repository at this point in the history
* Resolve `input` and `output` filenames to absolute paths
  when scheduling jobs, in case of future working directory change.
* Jobs and results now include `inputAbs` and `outputAbs` attributes
  with these absolute paths.  `input` and `output` remain the shorter
  names for showing to the user.
* All of the above is disabled when `settings.relative` is true.
* Fixes bug when process's working directory changes between job
  scheduling (which happens in SVG Tiler)
  • Loading branch information
edemaine committed Oct 25, 2022
1 parent 9ac5ed3 commit d6b6fa3
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 12 deletions.
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ Optional arguments:
--oP DIR / --output-png DIR Write all .png files to directory DIR
-i PATH / --inkscape PATH Specify PATH to Inkscape binary
--no-sanitize Don't sanitize PDF output by blanking out /CreationDate
--relative Run jobs with relative paths (default uses absolute)
-j N / --jobs N Run up to N Inkscape jobs in parallel
```

Expand Down Expand Up @@ -279,26 +280,33 @@ The resulting instance provides the following methods:
The output `formats` should be `"pdf"`, `".pdf"`, `"png"`, `".png"`,
another format/extension supported by Inkscape, or an array thereof.
It returns a promise, or an array of promises if `formats` is an array,
where each promise resolves to a `{skip, stdout, stderr, input, output}`
where each promise resolves to a
`{skip, stdout, stderr, input, output, inputAbs, outputAbs}`
object when the conversion and sanitization are complete.
Here either `skip` is `true` meaning that conversion was skipped because
the input was older than the output and `settings.force` was false, or
`stdout` and `stderr` give the string contents from Inkscape's stdout
and stderr for this job, which you should print to display warnings
and/or errors.
In addition, `input` is the original input filename,
and `output` is the generated output filename with `format` extension.
`output` is the generated output filename with `format` extension,
and `inputAbs` and `outputAbs` are the corresponding absolute paths
(unless `settings.relative` is true).
The promise also has an `output` property with the output filename
in case it's needed earlier.
* `convert(input, output)` queues converting one filename to another,
followed by sanitizing the output. The input file should be SVG.
The output format is determined from the file extension.
It returns a promise which resolves to a
`{stdout, stderr, skip, input, output}` object
`{stdout, stderr, skip, input, output, inputAbs, outputAbs}` object
when the conversion and sanitization are complete.
The absolute paths `inputAbs` and `outputAbs` are generated automatically,
unless `settings.relative` is true.
* `run(job)` queues a given job. A job can be a string to send to
Inkscape directly, an object with a `job` string property,
or an object of the form `{input: 'input.svg', output: 'output.pdf'}`,
or an object of the form `{input: 'input.svg', output: 'output.pdf',
inputAbs: '/full/path/input.svg', outputAbs: '/full/path/output.pdf}`
(where `inputAbs` and `outputAbs` are optional),
but scheduling a conversion in this way will skip sanitization
and force conversion (skip modification time checking).
Returns a promise which resolves to a `{stdout, stderr}` object,
Expand All @@ -313,6 +321,8 @@ The resulting instance provides the following methods:
Each input in `inputs` can be a glob or directory name, as in `convertGlob`.
To be notified of conversions and/or errors, you should listen to the
corresponding [events](#events).
This API does not currently normalize paths to absolute paths, so if you're
going to be changing `process.cwd()`, you should normalize yourself.
* `wait()` returns a promise which resolves when all jobs are complete.
Only one `wait()` or `close()` should be active at once.
* `close()` shuts down Inkscape processes once all conversion jobs
Expand Down Expand Up @@ -354,6 +364,10 @@ The constructors for `SVGProcessor` and `Inkscape` take a single optional
argument, which is a settings object. It can have the following properties:

* `force`: Whether to force conversion, even if SVG file is older than target.
* `relative`: Whether to run jobs with relative paths, or resolve to
absolute paths. Absolute paths support changing directories between jobs,
so this is the default, but relative paths are shorter and might bypass some
limitations (such as no `;` in a path name, or Cygwin quirks).
* `outputDir`: Default directory to output files via `convertTo`.
Default = `null` which means same directory as input.
* `outputDirExt`: Object mapping from extensions (`.pdf` or `.png`) to
Expand Down Expand Up @@ -403,10 +417,13 @@ supporting the following events:
which (or how many) filenames matched a glob pattern or directory.
* `'converted'` indicates that a file has just been successfully converted
and sanitized into a single format. The event has one argument,
a `{skip, stdout, stderr, input, output}` object as resolved from
`convertTo`. In particular:
a `{skip, stdout, stderr, input, output, inputAbs, outputAbs}` object
as resolved from `convert` or `convertTo`. In particular:
* `input` is the input filename.
* `output` is the output filename.
* `inputAbs` is the input absolute path (unless `settings.relative` is true).
* `outputAbs` is the output absolute path
(unless `settings.relative` is true).
* `skip` is a Boolean indicating whether conversion was skipped because
the input was older than the output and `settings.force` was false
* `stdout` and `stderr` give the string contents from Inkscape's stdout
Expand Down
23 changes: 17 additions & 6 deletions svgink.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ path = require 'path'
defaultSettings =
## Whether to force conversion, even if SVG file is older than target.
force: false
## Whether to run jobs with relative paths, or resolve to absolute paths.
## Absolute paths support changing directories between jobs, but relative
## paths are shorter and might bypass some limitations.
relative: false
## Directories to output all or some files.
outputDir: null ## default: same directory as input
outputDirExt: ## by extension; default is to use outputDir
Expand Down Expand Up @@ -166,11 +170,12 @@ class Inkscape
@cmd = job.job.replace /\n+$/, ''
else if job?.input? and job.output?
@cmd = [
"file-open:#{@job.input}"
"export-filename:#{job.output}"
"file-open:#{@job.inputAbs ? @job.input}"
"export-filename:#{job.outputAbs ? @job.output}"
'export-overwrite'
'export-do'
].join ';'
#console.log @cmd
else
throw new InkscapeError "Invalid Inkscape job: #{@job}"
@cmd += '\n'
Expand Down Expand Up @@ -266,9 +271,12 @@ class SVGProcessor extends EventEmitter
convert: (input, output) ->
## Convert input filename to output filename, and then sanitize,
## unless output is newer than input or forced. Returns a Promise.
unless @settings.relative
inputAbs = path.resolve input
outputAbs = path.resolve output
@jobs++
new Promise (resolve, reject) =>
for filename in [input, output]
for filename in [inputAbs ? input, outputAbs ? output]
if invalidFilename filename
@jobs--
reject new InkscapeError "Inkscape shell does not support filenames with semicolons or leading/trailing spaces: #{filename}"
Expand All @@ -280,11 +288,11 @@ class SVGProcessor extends EventEmitter
outputStat = await fs.stat output
inputStat = await fs.stat input
unless inputStat? and outputStat? and inputStat.mtime < outputStat.mtime
@queue.push {job: {input, output}, resolve, reject}
@queue.push {job: {input, output, inputAbs, outputAbs}, resolve, reject}
@update()
else
@jobs--
resolve {input, output, skip: true}
resolve {input, output, inputAbs, outputAbs, skip: true}
@update() if @waiting # resolve wait() in case last job is skipped
undefined
.then (result) =>
Expand Down Expand Up @@ -360,7 +368,7 @@ class SVGProcessor extends EventEmitter
inkscape.run job
.then (data) =>
@update()
await @sanitize job.output if job.output?
await @sanitize job.outputAbs ? job.output if job.output?
data
.then (data) =>
resolve data
Expand Down Expand Up @@ -484,6 +492,7 @@ Optional arguments:
--oP DIR / --output-png DIR Write all .png files to directory DIR
-i PATH / --inkscape PATH Specify PATH to Inkscape binary
--no-sanitize Don't sanitize PDF output by blanking out /CreationDate
--relative Run jobs with relative paths (default uses absolute)
-j N / --jobs N Run up to N Inkscape jobs in parallel
"""

Expand Down Expand Up @@ -544,6 +553,8 @@ main = (args = process.argv[2..]) ->
formats.push 'png'
when '--no-sanitize'
settings.sanitize = false
when '--relative'
settings.relative = true
when '-j', '--jobs'
skip = 1
arg = parseInt args[i+1]
Expand Down

0 comments on commit d6b6fa3

Please sign in to comment.