1

I'm working on an open source Rails 4 project that uses PDF Tool Kit ('pdf-forms' gem) to auto-fill PDF forms based on info stored in the app's database. PDFtk requires binaries to be installed, and the instantiated PDFtk model requires a path to the binaries. The path needs to be dynamic so it will work on Heroku, Mac OS X, and Windows. I can find the path on a *nix machine with the 'which' command. But, Windows doesn't use 'which', it uses 'where'.

Is there a way to detect if the platform responds to a command, and if so, then execute command?

My best attempt is to detect platform with RbConfig (see below). However, some Windows platforms (e.g. Cygwin) respond to *nix commands. As a Mac user I'm not familiar with every platform for Windows.

def pdftk
  # Use path stored in Heroku env vars or else get path to local binaries
  @pdftk ||= PdfForms.new(ENV['PDFTK_PATH'] || local_path) 
end

def local_path
  os = RbConfig::CONFIG['arch']
  if /mswin/ =~ os
    path = `where pdftk` # Get pdftk filepath, Windows equiv of *nix 'which' command
  else
    path = `which pdftk` # Get pdftk filepath on POSIX systems
  end

  path
end
femmestem
  • 551
  • 2
  • 9
  • Could you elaborate why would you need _Rails_ project to be run on Windows hosts? Are you going to ship the whole project to the end user? Whether the user is fine with installing rails project, I would go with mandatory setting in kinda `config.rb` for the path, with the default value set to where this library is installed “out of the box.” (Or with env setting as you have shown in the OP.) – Aleksei Matiushkin Mar 19 '16 at 07:10
  • @mudasobwa I am collaborating remotely with developers using Windows machines, where some are not able/willing to use Cygwin or similar. Using a `config.rb` with hardcoded file paths wouldn't eliminate the issue of platform detection. – femmestem Mar 19 '16 at 07:22
  • Fair enough. I did not mean hardcoded paths. I meant you might enforce the collaborators to change this file’s content or set the env variable. After all, they are developers, not customers. And, IMHO, this is not a codepiece that really needs to be polished. Platform detection as you have shown is already much better than nothing. – Aleksei Matiushkin Mar 19 '16 at 07:26
  • Ah, thanks for the clarification, that makes sense. – femmestem Mar 19 '16 at 07:31
  • 1
    _Sidenote:_ all three occurences of `path` in `local_path` source are redundant. `if` in ruby returns result. I would just eliminate everything starting with `if` and use `"#{/mswin/ =~ os ? 'where' : 'which'} pdftk"` instead. (Change double quotes to backticks there.) – Aleksei Matiushkin Mar 19 '16 at 07:35
  • Do you absolutely need to know the full path to `pdftk`? There are some [semi-solutions](http://stackoverflow.com/questions/304319/is-there-an-equivalent-of-which-on-the-windows-command-line) for the windows equivalent of the unix `which` command. However, why don't you simply try to call the `pdftk` command without any parameters and see the exit code in `$?` ? If you get a non-zero exit code, the program most likely does not exist on the platform. – BoraMa Mar 19 '16 at 08:58
  • There seems to be a gem for this sort of thing, talked abuot here: http://stackoverflow.com/questions/11784109/detecting-operating-systems-in-ruby – JLB Mar 19 '16 at 12:23
  • If you build an open source project, of course you want it to be as platform independent as possible. @mudasobwa – Meier Mar 19 '16 at 13:38
  • @Meier general sentences rarely are an absolute truth. One of the most famous open source project, Linux, is hardly cross-platform. Of course, I am not saying that one should hard-code `C:\ProgramFiles` or like, but in this case, taking into account that I did not suggest to get rid of Windows support, but make it by changing `config` file, this approach is OK. – Aleksei Matiushkin Mar 19 '16 at 16:31

2 Answers2

1

The where command should be available at least since Windows 7.

If you use jruby, the RbConfig::CONFIG['arch'] command will give you a java-related identifier. According to this question you can use host_os instead (tested with jruby 9000 in windows 7).

You can also make the the code more friendly to your coworkers by checking for errors of the call to the which/where command:

def local_path
  os = RbConfig::CONFIG['host_os']
  if /mswin/ =~ os
    path = `where pdftk` # Get pdftk filepath, Windows equiv of *nix 'which' command
  else
    path = `which pdftk` # Get pdftk filepath on POSIX systems
  end
  unless $?.success?
    log.error "failure to find the pdfkit excutable path"
    puts "Please set the PDFTK_PATH environment variable to point to the pdftkkit executable"
    path = ""  # maybe throw an exception here
  end
  path
end
Community
  • 1
  • 1
Meier
  • 3,773
  • 1
  • 14
  • 43
  • It's hard to accept **one** correct answer between yours and 10dot because I ended up using both in combination. I accepted 10dot's answer as correct because it was specific to what I was trying to accomplish. However, I'm also upvoting your answer for generally good advice on how to shift responsibility to the other devs to properly configure their environment, and enforce it with good error messages. – femmestem Mar 22 '16 at 01:08
1

The stdlib mkmf module has a little known helper function, find_executable() which should give you what you want

require 'mkmf'
path = find_executable('cli-command')

I haven't personally tested it on Windows, but I would assume it should work... http://ruby-doc.org/stdlib-2.3.0/libdoc/mkmf/rdoc/MakeMakefile.html#method-i-find_executable

10dot
  • 101
  • 6