Monday, August 25, 2008

Substitute

Another Ocaml script (although, I suppose it isn't a script, because I compile it). I really like to write in Ocaml, because it's a different kind of thought process.

This one is just a little tool that I sometimes like to use, when I have to go through a file and change the name of some array or similar. Granted, this can be done automatically through many text editors, but sometimes it's a hassle to even open a file, especially if it's biggish.

Besides, I found this bugger on my desktop today, in a slightly different form, and couldn't figure out what it was, that I wanted it to do (which is embarrassing, because it wasn't that hard). When I did figure out what it does, I decided to complicate it a bit, and use some of that fantastic functional paradigm, that Ocaml offers (lines 81, 84, and others).

I used a trick here, that I often use when doing functional programming. It's in the function on line 94 - handle_arguments. Inside this function I create a function with the same name. I can do this, because, unless I use the rec keyword during declaration, the function is not declared yet within itself. The inner function is recursive (the rec keyword), so that it can traverse a list and pass on the results, whilst the outer function calls it with proper arguments. Thanks to this, I don't have to remember to call handle_arguments with an additional parameter (an empty list for the results) from the outside, so the code is just that little bit cleaner.

Technical hints:
To compile the code just go:
ocamlc -o substitute str.cma substitute.ml

As you can see, it needs the additional Str module.

To run the program go:
ocamlc -o substitute str.cma substitute.ml


Oh yeah, and if you need a different format for the command line arguments, you can change the delimiter at line 28.

Warning: The substitution may fail somewhere, so if you try doing something important with this piece of software, better have a backup copy of whathever you're using it with.

The program:
1  (**
2   * Substitute
3   * Substitutes things for other things in files.
4   *
5   * Compiling:
6   *  ocamlc -o substitute str.cma substitute.ml
7   * Example usage:
8   *  echo '$0 $1!' | ./substitute '$0->Hello' '$1->world'
9   *  #=> Hello world!
10  * Potential issues:
11  *  May substitute more than you'd like, so keep a 
12  *  backup.
13  * Parameters:
14  *  Each parameter is in the form of a placeholder,
15  *  the delimiter '->' and a substitution
16  *  (best to look at the example).
17  * Requires:
18  *  Ocaml (http://caml.inria.fr/)
19  * Author:
20  *  Konrad Siek
21  *)
22 
23 (**
24  * Delimiter for program arguments.
25  * Establishes which part of the argument is the 
26  * placeholder and which is the substitution.
27  *)
28 let delimiter = "->";;
29 (**
30  * Substitutes a placeholder with a phrase.
31  * This function runs recursivelly on a list.
32  * @param what - placeholder
33  * @param into - substitution
34  * @param contents - array of strings to traverse
35  * @return an array of strings
36  *)
37 let substitute functions contents =
38     let rec apply functions content =
39         match functions with
40         | transform::tail -> 
41             apply tail (transform content)
42         | [] -> content
43     in
44     let result = ref [] in
45     let iterate element =
46         result := !result @ 
47         [apply functions element]
48     in
49     List.iter iterate contents;
50     !result
51 ;;
52 (**
53  * Outputs the contents of an array to standard
54  * output.
55  * @param contents - an array of strings
56  *)
57 let rec print_contents contents = 
58     match contents with
59     | head::tail -> 
60         print_endline head; 
61         print_contents tail
62     | [] -> () 
63 ;;
64 (**
65  * Converts a program argument into a translation
66  * function.
67  * @param argument
68  * @return a function
69  *)
70 let handle_argument argument = 
71     let regex = Str.regexp_string delimiter in
72     let bits = Str.split regex argument in 
73     if List.length bits < 2 then (
74         prerr_string 
75             ("Illegal argument: '" ^ argument ^ "'");
76         prerr_newline ();
77         fun input -> input
78     ) else (
79         let from = Str.regexp_string (List.hd bits) in 
80         let into = List.fold_left
81             (fun a b -> a ^ delimiter ^ b) 
82             (List.hd (List.tl bits)) 
83             (List.tl (List.tl bits)) in
84         fun input -> 
85             (Str.global_replace from into input)
86     )
87 ;;
88 (**
89  * Converts a list of program arguments into a
90  * list of translation functions.
91  * @param arguments
92  * @return functions
93  *)
94 let handle_arguments arguments = 
95     let rec handle_arguments arguments results = 
96         match arguments with
97         | head::tail -> 
98             let argument = 
99                 (handle_argument head) in 
100            handle_arguments tail
101                (results @ [argument])
102        | [] -> results
103    in
104    handle_arguments arguments []
105;;
106(**
107 * Grab input from standard input - read until
108 * an end of stream occurs.
109 * @param unit
110 * @return list of strings
111 *)
112let read_input () =
113    let list = ref [] in 
114    let _ = try 
115        while true do
116            let line = input_line stdin in
117            list := !list @ [line]
118        done
119    with _ -> () in 
120    !list
121;;    
122
123(* Convert argument vector to list *)
124let arguments = (List.tl (Array.to_list Sys.argv)) in
125(* Translate arguments to functions *)
126let functions = handle_arguments arguments in
127(* Read contents from standard input *)
128let contents = read_input () in 
129(* Apply transformations on contents *)
130let results = substitute functions contents in
131(* And print results to standard output *)
132print_contents results;;

No comments: