progedit: Programmatic File Editing in Racket

1Introduction

The progedit package is for programmatic editing of files via Racket programs.
For an example of programmatic editing, progedit was originally written so that the McFly Tools package could modify a user’s "info.rkt", such as by adding define forms that were missing, without modifying anything else in the file.

We expect that progedit will usually be used mostly with syntax objects, in this pattern:

Parse file into syntax objects.

Identify desired changes to the file, in terms of deletes, inserts, and replaces.

Apply all the desired changes to the file in a single read-write pass.

The progedit-file procedure provides a framework for the reading and writing
(retaining or restoring the original file contents on an error, and leaving a
backup file on success). The progedit procedure accepts a language for the changes, encoded as
dynamically generated lists, and applies the changes to the file.

For example, let’s say we have a file in a language like in file "myfile" below.

"myfile"

(assignhonorific"Dr.")

(assignname"John")

;(perpetually)

(assignage29)

And let’s say we want to write a program that accepts a file in this language, and makes sure that any name variable in the file is set to "Jane". Specifically, if it finds a (assignnamevalue) form in the file, it replaces value with "Jane"; and if it doesn’t find that form in the file, it adds a new (assignname"Jane") form to the file. Here’s such a program, using progedit-file and progedit:

This program will edit the above "myfile" to change "John" to "Jane", so the file becomes:

"myfile"

(assignhonorific"Dr.")

(assignname"Jane")

;(perpetually)

(assignage29)

Now, if we manually edit "myfile" to remove the (assignnamevalue) form altogether, it looks like:

"myfile"

(assignhonorific"Dr.")

;(perpetually)

(assignage29)

If we run our program again, it adds a new form to the end:

"myfile"

(assignhonorific"Dr.")

;(perpetually)

(assignage29)

(assignname"Jane")

Notice that, although this particular program parses the file using read-syntax, which doesn’t even see the comment in the file, the comment remains intact. progedit changes only the parts of the file it’s told to, and leaves
every other character in the file intact.

2Interface

The main engine for progedit is the progedit procedure. progedit will often be used in conjunction with the progedit-file procedure.

(default-progedit-write-stxstxout)→void

stx:syntax?

out:output-port?

Used as the default for the optional #:write-stx argument of progedit, this procedure writes stx as if it were in the Racket programming language.

(progedit

in

out

[

#:deletesdeletes

#:insertsinserts

#:replacesreplaces

#:write-stxwrite-stx])

→

void?

in:input-port?

out:output-port?

deletes:list?='()

inserts:list?='()

replaces:list?='()

write-stx

:

(->syntax?output-port?any)

=

default-progedit-write-stx

Performs a programmatic editing of the input read from in, writing the edited result to out. This is usually used in conjunction with progedit-file, which supplies the input and output ports.

The edits are specified by the language of the deletes, inserts, and replacements arguments. A BNF-like grammar for this language is:

deletes

=

(delete...)

inserts

=

(insert...)

replaces

=

(replace...)

delete

=

syntax

|

(position. position)

insert

=

(position. content)

replace

=

(delete. content)

position

=

exact-positive-integer

|

#f

|

(before. syntax)

|

(after. syntax)

|

(beforesyntax)

|

(aftersyntax)

content

=

syntax

|

string

|

byte-string

|

character

|

input-port

|

procedure

|

(content. content)

|

()

In general, you usually want to specify position either by a syntax object taken from a parse of the input, or by #f, meaning the end of the input. You can also use a number for
the character position, with the characters being numbered starting with 1.

For content, syntax objects are written as Racket code. Strings, byte strings, and characters are written verbatim. Input ports are written by copying their content to the output. Procedures are written by applying the procedure with out as an argument. Pairs are written by recursively writing their
CAR and CDR.

(default-progedit-file-backuppath)→path?

path:path-string?

This procedure is used as the default for the optional #:backup argument of progedit-file. It returns (path-add-suffixpath".bak") after deleting any such existing file.

(progedit-file

filename

#:readread-proc

#:writewrite-proc

[

#:backupbackup-proc])

→

any

filename:path-string?

read-proc:(->input-port?any)

write-proc:procedure?

backup-proc

:

(->path-string?path-string?)

=

default-progedit-file-backup

Applies read-proc and write-proc to read and then write file filename, creating a backup file named by calling the backup procedure argument with an argument of filename. Any error results in the file’s contents either being left
unchanged or being restored. Changes to the file during write-proc, such as another program modifying the file, results in an
error.

Symbolic links are followed, so the actual file is edited, and
any symbolic link remains unmodified.

read-proc is called with an argument of an input port on the contents of
the file. This is not necessarily on the file itself; it might be on a
copy of the file. The value or values returned by read-proc are appended to the arguments when write-proc is called.

write-proc is called with two arguments — an input port and an output-port – with an additional argument for each value returned by read-proc. Normally this will be one additional argument, unless a multiple-value return is used by write-proc. This provides a functional way to communicate information from read-proc to write-proc. The input port will be on the contents of the file before editing, and the write port will be for the contents of the file after editing. Normally, write-proc will use procedure progedit to handle these two ports.

3History

PLaneT 1:2 — 2012-06-11

Documentation fixes. (Thanks to Laurent Orseau.)

PLaneT 1:1 — 2012-06-11

Documentation fixes.

PLaneT 1:0 — 2012-06-11

Initial release.

4Legal

Copyright 2012 Neil Van Dyke. This program is Free Software; you can
redistribute it and/or modify it under the terms of the GNU Lesser General
Public License as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version. This program is
distributed in the hope that it will be useful, but without any warranty;
without even the implied warranty of merchantability or fitness for a
particular purpose. See http://www.gnu.org/licenses/ for details. For other
licenses and consulting, please contact the author.