1

I am writing several .gv files (graphviz) in the same directory and want to create a png file with neato immediately when I have saved one of them. I am on macOS 10.12.6, with zsh as my default shell, and I have installed entr from https://github.com/eradman/entr to monitor file changes. I have tried following command without any luck

$ echo $0
-zsh
$ ls *.gv | entr 'F="/_";neato -Tpng "$F" -o"${F/%\.gv/.png}"'
entr: exec F="/_";neato -Tpng "$F" -o"${F/%\.gv/.png}": No such file or directory

Following works

  1. Simple printing of last modified file

     $ ls *.gv | entr echo /_
     # Press <Space> to manually trigger event to see last changed file
     /Users/hotschke/complete.gv
    
  2. Using the same output name for all files:

     $ ls *.gv | entr neato -Tpng /_ -oConstantname.png
    
  3. Replace .gv with .png

     $ F="/Users/hotschke/complete.gv";neato -Tpng "$F" -o"${F/%\.gv/.png}"
    

Note the special argument /_ of entr

The special /_ argument (somewhat analogous to $_ in Perl) provides a quick way to refer to the first file that changed. When a single file is listed this is a handy way to avoid typing a pathname twice:

It would be great to have several answers using different tools (e.g. watchman, watchdog, fswatch, entr, launchd (mac-only); see also the discussion https://stackoverflow.com/q/1515730/)

Hotschke
  • 265
  • 2
  • 11
  • Why not use `inotifywait -m`, which outputs touched nodes, rather than `entr` which appears to consume a list of nodes to monitor. You can then use `sed` (or similar) to produce a command to run. – Attie Mar 24 '18 at 11:37
  • I do not mind to use inotifywait. However, as far as I can see it does not exist on macOS. – Hotschke Mar 24 '18 at 11:41
  • I would prefer a cross-platform solution, but using only builtin utilities is as good as this. – Hotschke Mar 24 '18 at 11:43
  • Huh, you used the `inotify` tag... – Attie Mar 24 '18 at 12:09
  • Attie: Sorry for the misleading tag! I did not find a good one. – Hotschke Mar 24 '18 at 13:31
  • Have you seen [fswatch](https://github.com/emcrisostomo/fswatch)? – Attie Mar 24 '18 at 13:47
  • @bertieb: It is the final step when combining the 2. and 3. command: replacing the constant output file name with the one derived from `/_` by replacing the extension `gv` with `png`. I think, it is possibly an issue of shell escaping. Does my initially mentioned command work for you? I would be surprised. – Hotschke Mar 24 '18 at 14:29
  • @bertieb: I have also added the error message to the question. – Hotschke Mar 24 '18 at 14:37
  • @Hotschke Looks like we both got as far as the Bitbucket repo- would you like to add Eic's explanation as an answer? I'm happy to close the loop but figured you should get the first crack :) – bertieb Mar 26 '18 at 21:13

4 Answers4

2

Call a script with entr

tl;dr: pass /_ as an argument to a script to Do The Thing™

Rather than trying to futz with special arguments and variables, pass /_ to a simple script (which itself can be a one liner) to do your png generation:

$  ls *.gv | entr ./update.sh /_

with update.sh along the lines of:

neato -Tpng "$1" -o"${1/%\.gv/.png}"

I tested the above approach with with a simple script to use imagemagick to convert an image, which worked without quotes, but it's better to leave them in in case of spaces etc in filenames.

As a side note, I tend to use an update script with entr anyway as it keeps things clearer to my mind. For example, I use entr to watch LaTeX source files and generate an updated PDF, the update script in that case runs xelatex, biber and also refreshes the PDF viewer (pkill -HUP mupdf).

bertieb
  • 7,344
  • 36
  • 42
  • 54
  • Thanks! In this circumstance however, I would prefer avoiding to create a script. If I would create a support file for building, I would write a Makefile and use the suggested workflow `$ ls *.gv | entr make`. The Makefile would work without entr or similar and text editors often support calling a makefile (e.g. vim). I use vim and could activate an autocommand for the event of saving `:au BufWritePost make`. If I want that make runs in the background, I can use vim-dispatch, asyncrun.vim, neomake, vim-accio or one of the many other plugins out there. – Hotschke Mar 24 '18 at 16:39
  • BTW, do you know [latexmk](https://ctan.org/pkg/latexmk)? It is part of TexLive. It offers the option `$ latexmk -pvc` which makes `entr` redundant and is very clever regarding calling the programs xelatex, biber and other just as often as needed and also refreshes the viewer. – Hotschke Mar 24 '18 at 16:42
  • @Hotschke fair point about makefiles (and good answer); I don't come from a programming background so it would be more work for me to learn their syntax -- on TODO list -- then write a one-to-three-ish liner depending on context; though I'll admit it feels a bit un-unixy to not use the right tool for the job. – bertieb Mar 25 '18 at 10:22
  • IMHO Makefile syntax is not really intuitive. It took me a while to learn it and I still have to look up the meaning of `$<`, `$+` and similar. I do not use it often enough and also prefer latexmk for latex and cmake for c/c++. – Hotschke Mar 25 '18 at 10:54
1

Using fswatch instead of entr

Thanks to @Attie for pointing fswatch out. Following works for me:

 $ fswatch -e ".*" -i "$PWD/[^.]*\\.gv$" -0 $PWD |
     xargs -0 -n 1 -I {} sh -c 'F={}; neato -Tpng "$F" -o"${F/%\.gv/.png}"'

fswatch considers by default all files: you need to use filters to limit to a certain file type (see https://stackoverflow.com/q/34713278/).

https://emcrisostomo.github.io/fswatch/ by Enrico M. Crisostomo (2013-2017)

Hotschke
  • 265
  • 2
  • 11
1

Using a Makefile

SOURCES:=$(shell find . -maxdepth 1 -name '*.gv')

ifndef OTYPE
    OTYPE:=png
endif
# use `make OTYPE=pdf` to generate pdfs

TARGETS:=$(SOURCES:.gv=.$(OTYPE))

.PHONY: all clean

all: $(TARGETS)

clean:
    rm $(TARGETS)

%.$(OTYPE) : %.gv
    neato -T$(OTYPE) $< -o$(basename $<).$(OTYPE)

Now you can use

$ ls *.gv | entr make

There is room for improvement: there a two commands to list files with the extension .gv which contradicts a single source of truth (SSOT).

Vim Notes

You can also use the Makefile inside the text editor vim

:mak[e]

You can automatically call make on save by setting up an autocommand with

:au BufWritePost <buffer> make

You might consider to use a plugin for runnning make async: see the plugins vim-dispatch (normal mode mapping m<CR>), neomake, or asyncrun.vim.

However, there are already compiler definitions for the commands dot and neato:

This means you do not need to write a Makefile. You can set the makeprg by :comp[iler] neato or :comp dot. Note that you see all compiler definitions by :comp <C-d> and you can tab-complete :comp n<Tab> to :comp neato and :comp d<tab> to :comp dot.

Now you have to call make with an argument to specifiy your output format:

:make png
:au BufWritePost <buffer> make png

If you use vim-dispatch, this looks like

:Make png
m<Space>png<CR>
:au BufWritePost <buffer> Make png
Hotschke
  • 265
  • 2
  • 11
0

Entr – Pass /_ as an argument to a shell

ls *.gv | entr bash -c 'neato -Tpng "$0" -o"${0/%\.gv/.png}"' /_

This answer is from https://bitbucket.org/eradman/entr/issues/75/run-command-on-single-file-when-changed#comment-44183153.

This command silently executes the command neato. If you want to have feedback on the command line when entr detects a file change, add -x to bash to see the executed command:

ls *.gv | entr bash -x -c 'neato -Tpng "$0" -o"${0/%\.gv/.png}"' /_
Hotschke
  • 265
  • 2
  • 11