Caleb
Caleb

Reputation: 25

Save colored emacs text from shell

I am attempting to save a few hundred short files (~100 lines ea.) as a Post Script (or pdf) file using the colors as emacs renders them. So I am writing a bash script that opens them in emacs and saves them as a .ps file.

My best attempt so far is to iteratively use the following command:

emacs --file $(filename).m --no-desktop --eval '(ps-print-buffer-with-faces)' --eval '(kill-emacs)' 

However, this directly sends it to the default printer. I'd like to save it as $(filename)_PS.ps. Within the emacs gui, I can do

C-u M-x ps-print-buffer-with-faces

and it brings up the file save prompt. However doing this by hand is cumbersome. How might this be done with a shell script?

Upvotes: 1

Views: 400

Answers (1)

Tim X
Tim X

Reputation: 4235

The easy answer is to just provide the code, but then I'm the one learning and your still having to ask each time you need help with Emacs! Much more fun for me to try and point you in the right direction - probably less fun for you initially, but if I get it right, you won't need to ask as many questions in the future and then have to wait for another opinionated answer like this one :)

The most important skill to learn with Emacs is how to use the built-in help facility. The second most important is to learn some basic Emacs lisp by reading the Emacs Lisp Reference (bundled with Emacs). The third is learning to scratch your own itch.

You have the basic function, so we can start there. C-h f ps-print-buffer-with-faces tells us....

ps-print-buffer-with-faces is an interactive autoloaded Lisp function in ‘ps-print.el’.

It is bound to .

(ps-print-buffer-with-faces &optional FILENAME)

Generate and print a PostScript image of the buffer. Like ‘ps-print-buffer’, but includes font, color, and underline information in the generated image. This command works only if you are using a window system, so it has a way to determine color values.

OK, but at first glance, there doesn't seem to be much useful info there. However, there is a link to ps-print-buffer which is clickable. Clicking on it we get...

ps-print-buffer is an interactive autoloaded Lisp function in ‘ps-print.el’.

It is bound to .

(ps-print-buffer &optional FILENAME)

Generate and print a PostScript image of the buffer.

Interactively, when you use a prefix argument (C-u), the command prompts the user for a file name, and saves the PostScript image in that file instead of sending it to the printer.

Noninteractively, the argument FILENAME is treated as follows: if it is nil, send the image to the printer. If FILENAME is a string, save the PostScript image in a file with that name.

This gives us a bit more, including important details on how calling it non-interactively, the argument is the name of the file to write the output to. This seems promising as we don't want to be forced to call this interactively using C-u for every one of the many files we want to print.

If we could define a new command which would take care of this, we could just use M-x - even better, we could bind it to a key and then we only need to use that shortcut.

Looking in the Emacs Lisp Reference (also bundled with Emacs), we see the menu option Command Loop. Looking in that node, we see a menu option for Defining Commands. This looks promising. After reading that node and following some links in that node about defining functions etc, we get a basic starting point of ...

(defun buffer-to-ps ()
  (interactive)
  ;; do something
  )

We put that in the scratch buffer and evaluate it. Not terribly exciting. Lets add some documentation so we can remember what this is for ...

(defun buffer-to-ps ()
  "Write the current buffer to a PS file"
  (interactive)
  ;; do something
  )

Now we can do C-h f buffer-to-ps and we will see our documentation. Still, not doing a lot. We need to call our function and provide it with a file name argument. We try ...

(defun buffer-to-ps ()
  "Write the current buffer to a PS file"
  (interactive)
  (ps-print-buffer-with-faces "test.ps"))

With the cursor at the end of the definition, while in the scratch buffer, we do C-x C-e to evaluate our definition. We try running our new command while in the scratch buffer, we do M-x buffer-to-ps and we see a message in the echo area which says a file call test.ps has been written. Opening that file with a viewer and we see the file has been created and it has the colours etc. However, we don't want all the files to be called test.ps, so now we need to find a way to generate a unique name to use as the file name argument. There are a few ways we could do this, but the first obvious one is to somehow try and use the name of the buffer. We use C-h a and enter "buffer name", which gives a few hits. Going through the list, we see buffer-name. Doing C-h f buffer-name gives us

buffer-name is a built-in function in ‘C source code’.

(buffer-name &optional BUFFER)

Return the name of BUFFER, as a string. BUFFER defaults to the current buffer. Return nil if BUFFER has been killed.

Looks promising, so let's give it a go. ...

(defun buffer-to-ps ()
  "Write the current buffer to a PS file"
  (interactive)
  (ps-print-buffer-with-faces (buffer-name)))

Evaluating the definition and running it with M-x results in a new file being created called scratch. Looking at it with a ps viewer and we see it is our buffer contents with colour etc. So we have done it!

But wait, we have a problem Huston! If we run this with a buffer which contains contents from a file, the buffer name will be the same as the file name and when we run our command, it will overwrite our file - probably not what we want.

We probably want the output file to be similar to the corresponding script, but we also probably want it to reflect that it is a postscript rendering of that script. There are a few things we can try, but being lazy, we will go for the easiest first. What about if we just generate a file name which has ".ps" appended to it. This will also mean we have a file name with an extension which is a better fit with file name conventions. How do we concatenate a ".ps" to the file name string returned by buffer-name? C-h a concat and we get

concat is a built-in function in ‘C source code’.

(concat &rest SEQUENCES)

Concatenate all the arguments and make the result a string. The result is a string whose elements are the elements of all the arguments. Each argument may be a string or a list or vector of characters (integers).

So we update our function to be ...

(defun buffer-to-ps ()
  "Write the current buffer to a PS file"
  (interactive)
  (ps-print-buffer-with-faces (concat (buffer-name) ".ps")))

We use M-x to try it out and end up with the file scratch.ps.

So we now have the basic functionality. We could bind this command to a key sequence so that when we are in a buffer, we just hit the key sequence and emacs will generate a file with the extension .ps based on the current buffer name and which contains a postscript rendering of the buffer contents with the corresponding face colours.

What next? Well, using this function requires that we first open the file in a buffer and then call the function (possibly just by hitting a key). We could do it on the command line as your earlier version shows, but starting emacs each time just to do this is woefully inefficient. What would be a better approach?

One thing to try would be to define a function which lists all the files in a directory with a particular file name pattern i.e. all script files and for each of them, load them into a buffer and then call the function you have just defined. This is left as an exercise for the reader! Post an update to this question or create a new one and perhaps someone will provide some more help :)

Upvotes: 2

Related Questions