I’m trying to use the javascript mermaid command line utility to generate svg graphs from text. From the windows powershell command line,
"graph TD
>> A[Start] --> B{Is it working?}
>> B -- Yes --> C[Great!]
>> B -- No --> D[Fix it]
>> D --> B" | mmdc -i - -o - | echo
this works.
When I run the following code (mostly generated by ChatGPT)),
heredoc = <<-MERMAID
graph TD
A[Start] --> B{Is it working?}
B -- Yes --> C[Great!]
B -- No --> D[Fix it]
D --> B
MERMAID
stdin_io = IO::Memory.new
stdout_io = IO::Memory.new
stderr_io = IO::Memory.new
stdin_io.puts heredoc
command = "mmdc"
args = ["-i","-","-o","-"] of String
Process.run(command, args: args, input: stdin_io, output: stdout_io, error: stderr_io)
puts "STDOUT:"
puts stdout_io.to_s
unless stderr_io.to_s.empty?
puts "STDERR:"
puts stderr_io.to_s
end
I get an error: Unhandled exception: Error executing process: ‘mmdc -i - -o -’: The system cannot find the file specified. (File::NotFoundError).
How should I be formatting and organizing these STDIN and STDOUT to get this working from within Crystal?
Try doing like input: stdin_io.rewind
. Maybe the error is because the IO::Memory
is reading from the end and mmdc
doesn’t like there’s no data to read?
EDIT: Or more ideally instead of doing stdin_io.puts heredoc
do stdin_io = IO::Memory.new heredoc
, then won’t have to do the #rewind
call.
Alternatively:
stdin_io = IO::Memory.new(heredoc)
That way you don’t have to write to it and then rewind, it’s simply pre-populated with the heredoc contents.
Thanks, that’s gets me past the error. My output though is blank:
STDIN:
graph TD
A[Start] --> B{Is it working?}
B -- Yes --> C[Great!]
B -- No --> D[Fix it]
D --> B
STDOUT:
What code did you change to get that output? If you’re reading stdin_io
before running Process.run
then you’re consuming the whole thing and would have to run #rewind
again even if you did IO::Memory.new heredoc
. I made that one change with your previous code and get:
STDOUT:
<svg aria-roledescription="flowchart-v2" role="graphics-document document" viewBox="0 0 269.09375 395.46875" style="max-width: 269.094px; background-color: white;" class="flowchart" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="100%" id="my-svg">...</svg>
STDERR:
No output format specified, using svg. If you want to specify an output format and suppress this warning, please use `-e <format>.`
Oops, rookie mistake, I commented out the Process.run statement to ensure the stdin_io matched the command line example (and it did). When I remove the comment, I’m getting the same error. I changed the stdin_io line to stdin_io = IO::Memory.new(heredoc). My code is now:
heredoc = <<-MERMAID
graph TD
A[Start] --> B{Is it working?}
B -- Yes --> C[Great!]
B -- No --> D[Fix it]
D --> B
MERMAID
stdin_io = IO::Memory.new(heredoc)
stdout_io = IO::Memory.new
stderr_io = IO::Memory.new
command = "mmdc"
args = ["-i","-","-o","-"] of String
Process.run(command, args: args, input: stdin_io, output: stdout_io, error: stderr_io)
puts "STDOUT:"
puts stdout_io.to_s
unless stderr_io.to_s.empty?
puts "STDERR:"
puts stderr_io.to_s
end
I just tried it in PowerShell, Git Bash, and Command Prompt, same error. Are you on Windows?
I’m on a mac at the moment. Your latest code just works for me so maybe it’s Windows specific?
Looks like I’m bumping up against the experimental nature of Windows support. Looks like there are 3-4 important issues with command prompt or Process.run that are in the works.
The only gaps on Windows related to Process.run
are about the shell: true
parameter. You’re not using that. What you want to do should certainly work on Windows.
All the advice given here about the standard streams is good and helpful.
But it’s unrelated to the reported error: Error executing process: ‘mmdc -i - -o -’: The system cannot find the file specified. (File::NotFoundError).
This message indicates that the executable mmdc
could not be found. A possible cause could be it’s missing from PATH
in the environment of the Crystal program.
Do you run the Crystal program in the exact same shell as the original command line?
Yes, it is executed in the same shell. I can run the piped command line version and get results and then execute crystal run ./src/source.cr and get the error.
From the Powershell prompt I can issue (Get-Command mmdc).path and it returns the mmdc.ps1 file location.
I tried to use the full path to mmdc in Process.run and it provided the same file not found error.
What determines the message in this context? Like if the command is i_dont_exist
, would the error that gets raised be the same on all OSs because it comes from Crystal itself? When I read the error I was assuming it was coming from the executed command. But yea, after testing passing an actually empty IO
I do get a different error on STDERR
.
That explains it then!
The Windows API to execute processes, CreateProcessW
, (which Process.run
uses under the hood) can only run native executables (.exe
) directly.
A script such as .ps1
must be launched through an interpreter.
This is different from Unix systems, where the OS can run any script directly by implicitly executing its interpreter.
This means on Windows, it’s a more serious difference between executing a command in a shell and outside.
On top of that, PowerShell allows omitting the extension (e.g. .ps1
) when executing a script. That makes it look like you’re calling an actual executable. And it’s surprising when the same thing doesn’t work outside a shell.
These are unfortunate circumstances of the Windows platform, often unexpected when coming from the unix world.
You can run mmdc.ps1
through PowerShell. Something like this should work:
command = "powershell"
args = ["-Command", "mmdc -i - -o -"]
Process.run(command, args: args, ...)
1 Like
Error executing process
means Process.run
could not even execute the command. In this case, because it could not locate a valid executable.
1 Like
It’s the little details that get you. Much appreciate @straight-shoota, @Blacksmoke16 , and @jgaskins weighing in on this topic.