summaryrefslogtreecommitdiffstats
path: root/virt-top
diff options
context:
space:
mode:
authorRichard W.M. Jones <rjones@redhat.com>2007-09-24 16:13:58 +0100
committerRichard W.M. Jones <rjones@redhat.com>2007-09-24 16:13:58 +0100
commitb2c4dbaf7a3f9da9e4236fc46cf72b0ef4ee300d (patch)
tree10dc1a590c6df3c2607a485306db1db69124fe9e /virt-top
parent4a3610a5813163d322b304d56b41bb68dbe83513 (diff)
downloadvirt-top-b2c4dbaf7a3f9da9e4236fc46cf72b0ef4ee300d.tar.gz
virt-top-b2c4dbaf7a3f9da9e4236fc46cf72b0ef4ee300d.tar.xz
virt-top-b2c4dbaf7a3f9da9e4236fc46cf72b0ef4ee300d.zip
Version 0.3.2.8.
Added support for init files.
Diffstat (limited to 'virt-top')
-rw-r--r--virt-top/.depend6
-rw-r--r--virt-top/Makefile.in4
-rw-r--r--virt-top/virt-top.170
-rw-r--r--virt-top/virt-top.pod88
-rw-r--r--virt-top/virt-top.txt65
-rw-r--r--virt-top/virt_top.ml156
-rw-r--r--virt-top/virt_top_utils.ml74
7 files changed, 448 insertions, 15 deletions
diff --git a/virt-top/.depend b/virt-top/.depend
index 75ecf81..5879564 100644
--- a/virt-top/.depend
+++ b/virt-top/.depend
@@ -2,7 +2,9 @@ virt_top_csv.cmo: virt_top.cmo ../libvirt/libvirt.cmi
virt_top_csv.cmx: virt_top.cmx ../libvirt/libvirt.cmx
virt_top_main.cmo: virt_top.cmo ../libvirt/libvirt.cmi
virt_top_main.cmx: virt_top.cmx ../libvirt/libvirt.cmx
-virt_top.cmo: ../libvirt/libvirt_version.cmi ../libvirt/libvirt.cmi
-virt_top.cmx: ../libvirt/libvirt_version.cmx ../libvirt/libvirt.cmx
+virt_top.cmo: virt_top_utils.cmo ../libvirt/libvirt_version.cmi \
+ ../libvirt/libvirt.cmi
+virt_top.cmx: virt_top_utils.cmx ../libvirt/libvirt_version.cmx \
+ ../libvirt/libvirt.cmx
virt_top_xml.cmo: virt_top.cmo ../libvirt/libvirt.cmi
virt_top_xml.cmx: virt_top.cmx ../libvirt/libvirt.cmx
diff --git a/virt-top/Makefile.in b/virt-top/Makefile.in
index e1cb75f..1e5d48f 100644
--- a/virt-top/Makefile.in
+++ b/virt-top/Makefile.in
@@ -14,9 +14,9 @@ pkg_curses = @pkg_curses@
pkg_xml_light = @pkg_xml_light@
pkg_csv = @pkg_csv@
-OCAMLCPACKAGES := -package unix,extlib,curses
+OCAMLCPACKAGES := -package unix,extlib,curses,str
-OBJS := virt_top.cmo
+OBJS := virt_top_utils.cmo virt_top.cmo
ifeq ($(pkg_xml_light),yes)
OBJS += virt_top_xml.cmo
OCAMLCPACKAGES := $(OCAMLCPACKAGES),xml-light
diff --git a/virt-top/virt-top.1 b/virt-top/virt-top.1
index 7e9c5d4..3015f1f 100644
--- a/virt-top/virt-top.1
+++ b/virt-top/virt-top.1
@@ -129,7 +129,7 @@
.\" ========================================================================
.\"
.IX Title "VIRT-TOP 1"
-.TH VIRT-TOP 1 "2007-08-30" "ocaml-libvirt-0.3.2.6" "Virtualization Support"
+.TH VIRT-TOP 1 "2007-09-24" "ocaml-libvirt-0.3.2.8" "Virtualization Support"
.SH "NAME"
virt\-top \- 'top'\-like utility for virtualization stats
.SH "SUMMARY"
@@ -224,6 +224,13 @@ To send error messages to syslog you can do:
.Ve
.Sp
See also \s-1REPORTING\s0 \s-1BUGS\s0 below.
+.IP "\fB\-\-init\-file filename\fR" 4
+.IX Item "--init-file filename"
+Read \fIfilename\fR as the init file instead of the default which is
+\&\fI$HOME/.virt\-toprc\fR. See also \s-1INIT\s0 \s-1FILE\s0 below.
+.IP "\fB\-\-no\-init\-file\fR" 4
+.IX Item "--no-init-file"
+Do not read any init file.
.IP "\fB\-\-help\fR" 4
.IX Item "--help"
Display usage summary.
@@ -276,6 +283,67 @@ Sort by domain \s-1ID\s0.
.IX Item "F"
Select the sort field interactively (there are other
sort fields you can choose using this key).
+.IP "\fIW\fR" 4
+.IX Item "W"
+This creates or overwrites the init file with the current settings.
+.Sp
+This key is disabled if \fI\-\-no\-init\-file\fR was specified on the
+command line or if \fIoverwrite-init-file false\fR is given in
+the init file.
+.SH "INIT FILE"
+.IX Header "INIT FILE"
+When virt-top starts up, it reads initial settings from the
+file \fI.virt\-toprc\fR in the user's home directory.
+.PP
+The name of this file may be overridden using the \fI\-\-init\-file
+filename\fR command line option or may be disabled entirely using
+\&\fI\-\-no\-init\-file\fR.
+.PP
+The init file has a simple format. Blank lines and comments
+beginning with \fI#\fR are ignored. Everything else is a set of
+\&\fIkey value\fR pairs, described below.
+.IP "\fBdisplay\fR \fItask|pcpu|block|net\fR" 4
+.IX Item "display task|pcpu|block|net"
+Sets the major display mode to one of \fItask\fR (tasks, the
+default), \fIpcpu\fR (physical CPUs), \fIblock\fR (block devices),
+or \fInet\fR (network interfaces).
+.IP "\fBdelay\fR \fIsecs\fR" 4
+.IX Item "delay secs"
+Sets the delay between display updates in seconds.
+.IP "\fBhist-cpu\fR \fIsecs\fR" 4
+.IX Item "hist-cpu secs"
+Sets the historical \s-1CPU\s0 delay in seconds.
+.IP "\fBiterations\fR \fIn\fR" 4
+.IX Item "iterations n"
+Sets the number of iterations to run before we exit. Setting
+this to \fI\-1\fR means to run continuously.
+.IP "\fBsort\fR \fIcpu|mem|time|id|name|...\fR" 4
+.IX Item "sort cpu|mem|time|id|name|..."
+Sets the sort order. The option names are the same as for
+the command line \fI\-o\fR option.
+.IP "\fBconnect\fR \fIuri\fR" 4
+.IX Item "connect uri"
+Sets the default connection \s-1URI\s0.
+.IP "\fBdebug\fR \fIfilename\fR" 4
+.IX Item "debug filename"
+Sets the default filename to use for debug and error messages.
+.IP "\fBcsv\fR \fIfilename\fR" 4
+.IX Item "csv filename"
+Enables \s-1CSV\s0 output to the named file.
+.IP "\fBbatch\fR \fItrue|false\fR" 4
+.IX Item "batch true|false"
+Sets batch mode.
+.IP "\fBsecure\fR \fItrue|false\fR" 4
+.IX Item "secure true|false"
+Sets secure mode.
+.IP "\fBoverwrite-init-file\fR \fIfalse\fR" 4
+.IX Item "overwrite-init-file false"
+If set to \fIfalse\fR then the \fIW\fR key will not overwrite the
+init file.
+.PP
+Note that in the current implementation, options specified in
+the init file override options specified on the command line.
+This is a bug and this behaviour may change in the future.
.SH "SEE ALSO"
.IX Header "SEE ALSO"
\&\fItop\fR\|(1),
diff --git a/virt-top/virt-top.pod b/virt-top/virt-top.pod
index fe8ba91..3ec0dac 100644
--- a/virt-top/virt-top.pod
+++ b/virt-top/virt-top.pod
@@ -108,6 +108,15 @@ To send error messages to syslog you can do:
See also REPORTING BUGS below.
+=item B<--init-file filename>
+
+Read I<filename> as the init file instead of the default which is
+I<$HOME/.virt-toprc>. See also INIT FILE below.
+
+=item B<--no-init-file>
+
+Do not read any init file.
+
=item B<--help>
Display usage summary.
@@ -179,8 +188,87 @@ Sort by domain ID.
Select the sort field interactively (there are other
sort fields you can choose using this key).
+=item I<W>
+
+This creates or overwrites the init file with the current settings.
+
+This key is disabled if I<--no-init-file> was specified on the
+command line or if I<overwrite-init-file false> is given in
+the init file.
+
=back
+=head1 INIT FILE
+
+When virt-top starts up, it reads initial settings from the
+file I<.virt-toprc> in the user's home directory.
+
+The name of this file may be overridden using the I<--init-file
+filename> command line option or may be disabled entirely using
+I<--no-init-file>.
+
+The init file has a simple format. Blank lines and comments
+beginning with I<#> are ignored. Everything else is a set of
+I<key value> pairs, described below.
+
+=over 4
+
+=item B<display> I<task|pcpu|block|net>
+
+Sets the major display mode to one of I<task> (tasks, the
+default), I<pcpu> (physical CPUs), I<block> (block devices),
+or I<net> (network interfaces).
+
+=item B<delay> I<secs>
+
+Sets the delay between display updates in seconds.
+
+=item B<hist-cpu> I<secs>
+
+Sets the historical CPU delay in seconds.
+
+=item B<iterations> I<n>
+
+Sets the number of iterations to run before we exit. Setting
+this to I<-1> means to run continuously.
+
+=item B<sort> I<cpu|mem|time|id|name|...>
+
+Sets the sort order. The option names are the same as for
+the command line I<-o> option.
+
+=item B<connect> I<uri>
+
+Sets the default connection URI.
+
+=item B<debug> I<filename>
+
+Sets the default filename to use for debug and error messages.
+
+=item B<csv> I<filename>
+
+Enables CSV output to the named file.
+
+=item B<batch> I<true|false>
+
+Sets batch mode.
+
+=item B<secure> I<true|false>
+
+Sets secure mode.
+
+=item B<overwrite-init-file> I<false>
+
+If set to I<false> then the I<W> key will not overwrite the
+init file.
+
+=back
+
+
+Note that in the current implementation, options specified in
+the init file override options specified on the command line.
+This is a bug and this behaviour may change in the future.
+
=head1 SEE ALSO
L<top(1)>,
diff --git a/virt-top/virt-top.txt b/virt-top/virt-top.txt
index ee2a426..7eeb1cd 100644
--- a/virt-top/virt-top.txt
+++ b/virt-top/virt-top.txt
@@ -80,6 +80,13 @@ OPTIONS
See also REPORTING BUGS below.
+ --init-file filename
+ Read *filename* as the init file instead of the default which is
+ *$HOME/.virt-toprc*. See also INIT FILE below.
+
+ --no-init-file
+ Do not read any init file.
+
--help
Display usage summary.
@@ -122,6 +129,64 @@ KEYS
*F* Select the sort field interactively (there are other sort fields you
can choose using this key).
+ *W* This creates or overwrites the init file with the current settings.
+
+ This key is disabled if *--no-init-file* was specified on the
+ command line or if *overwrite-init-file false* is given in the init
+ file.
+
+INIT FILE
+ When virt-top starts up, it reads initial settings from the file
+ *.virt-toprc* in the user's home directory.
+
+ The name of this file may be overridden using the *--init-file filename*
+ command line option or may be disabled entirely using *--no-init-file*.
+
+ The init file has a simple format. Blank lines and comments beginning
+ with *#* are ignored. Everything else is a set of *key value* pairs,
+ described below.
+
+ display *task|pcpu|block|net*
+ Sets the major display mode to one of *task* (tasks, the default),
+ *pcpu* (physical CPUs), *block* (block devices), or *net* (network
+ interfaces).
+
+ delay *secs*
+ Sets the delay between display updates in seconds.
+
+ hist-cpu *secs*
+ Sets the historical CPU delay in seconds.
+
+ iterations *n*
+ Sets the number of iterations to run before we exit. Setting this to
+ *-1* means to run continuously.
+
+ sort *cpu|mem|time|id|name|...*
+ Sets the sort order. The option names are the same as for the
+ command line *-o* option.
+
+ connect *uri*
+ Sets the default connection URI.
+
+ debug *filename*
+ Sets the default filename to use for debug and error messages.
+
+ csv *filename*
+ Enables CSV output to the named file.
+
+ batch *true|false*
+ Sets batch mode.
+
+ secure *true|false*
+ Sets secure mode.
+
+ overwrite-init-file *false*
+ If set to *false* then the *W* key will not overwrite the init file.
+
+ Note that in the current implementation, options specified in the init
+ file override options specified on the command line. This is a bug and
+ this behaviour may change in the future.
+
SEE ALSO
top(1), virsh(1), xm(1), xentop(1), <http://www.libvirt.org/>,
<http://et.redhat.com/~rjones/>, <http://caml.inria.fr/>
diff --git a/virt-top/virt_top.ml b/virt-top/virt_top.ml
index a5953f3..121c12e 100644
--- a/virt-top/virt_top.ml
+++ b/virt-top/virt_top.ml
@@ -6,6 +6,8 @@ open Printf
open ExtList
open Curses
+open Virt_top_utils
+
module C = Libvirt.Connect
module D = Libvirt.Domain
module N = Libvirt.Network
@@ -32,6 +34,7 @@ let (-^) = Int64.sub
let ( *^ ) = Int64.mul
let (/^) = Int64.div
+(* Sort order. *)
type sort_order =
| DomainID | DomainName | Processor | Memory | Time
| NetRX | NetTX | BlockRdRq | BlockWrRq
@@ -49,10 +52,44 @@ let printable_sort_order = function
| NetTX -> "Net TX bytes"
| BlockRdRq -> "Block read reqs"
| BlockWrRq -> "Block write reqs"
+let sort_order_of_cli = function
+ | "cpu" | "processor" -> Processor
+ | "mem" | "memory" -> Memory
+ | "time" -> Time
+ | "id" -> DomainID
+ | "name" -> DomainName
+ | "netrx" -> NetRX | "nettx" -> NetTX
+ | "blockrdrq" -> BlockRdRq | "blockwrrq" -> BlockWrRq
+ | str -> failwith (str ^ ": sort order should be: cpu|mem|time|id|name|netrx|nettx|blockrdrq|blockwrrq")
+let cli_of_sort_order = function
+ | Processor -> "cpu"
+ | Memory -> "mem"
+ | Time -> "time"
+ | DomainID -> "id"
+ | DomainName -> "name"
+ | NetRX -> "netrx"
+ | NetTX -> "nettx"
+ | BlockRdRq -> "blockrdrq"
+ | BlockWrRq -> "blockwrrq"
(* Current major display mode: TaskDisplay is the normal display. *)
type display = TaskDisplay | PCPUDisplay | BlockDisplay | NetDisplay
+let display_of_cli = function
+ | "task" -> TaskDisplay
+ | "pcpu" -> PCPUDisplay
+ | "block" -> BlockDisplay
+ | "net" -> NetDisplay
+ | str -> failwith (str ^ ": display should be task|pcpu|block|net")
+let cli_of_display = function
+ | TaskDisplay -> "task"
+ | PCPUDisplay -> "pcpu"
+ | BlockDisplay -> "block"
+ | NetDisplay -> "net"
+
+(* Init file. *)
+type init_file = NoInitFile | DefaultInitFile | InitFile of string
+
(* Settings. *)
let quit = ref false
let delay = ref 3000 (* milliseconds *)
@@ -65,6 +102,7 @@ let display_mode = ref TaskDisplay
let uri = ref None
let debug_file = ref ""
let csv_enabled = ref false
+let init_file = ref DefaultInitFile
(* Function to read command line arguments and go into curses mode. *)
let start_up () =
@@ -74,22 +112,15 @@ let start_up () =
failwith "-d: cannot set a negative delay";
delay := int_of_float (newdelay *. 1000.)
and set_uri = function "" -> uri := None | u -> uri := Some u
- and set_sort = function
- | "cpu" | "processor" -> sort_order := Processor
- | "mem" | "memory" -> sort_order := Memory
- | "time" -> sort_order := Time
- | "id" -> sort_order := DomainID
- | "name" -> sort_order := DomainName
- | "netrx" -> sort_order := NetRX | "nettx" -> sort_order := NetTX
- | "blockrdrq" -> sort_order := BlockRdRq
- | "blockwrrq" -> sort_order := BlockWrRq
- | str -> failwith (str ^ ": sort order should be: cpu|mem|time|id|name|netrx|nettx|blockrdrq|blockwrrq")
+ and set_sort order = sort_order := sort_order_of_cli order
and set_pcpu_mode () = display_mode := PCPUDisplay
and set_net_mode () = display_mode := NetDisplay
and set_block_mode () = display_mode := BlockDisplay
and set_csv filename =
(!csv_start) filename;
csv_enabled := true
+ and no_init_file () = init_file := NoInitFile
+ and set_init_file filename = init_file := InitFile filename
in
let argspec = Arg.align [
"-1", Arg.Unit set_pcpu_mode, " Start by displaying pCPUs (default: tasks)";
@@ -102,6 +133,8 @@ let start_up () =
"-d", Arg.Float set_delay, "delay Delay time interval (seconds)";
"--debug", Arg.Set_string debug_file, "file Send debug messages to file";
"--hist-cpu", Arg.Set_int historical_cpu_delay, "secs Historical CPU delay";
+ "--init-file", Arg.String set_init_file, "file Set name of init file";
+ "--no-init-file", Arg.Unit no_init_file, " Do not read init file";
"-n", Arg.Set_int iterations, "iterations Number of iterations to run";
"-o", Arg.String set_sort, "sort Set sort order (cpu|mem|time|id|name)";
"-s", Arg.Set secure_mode, " Secure (\"kiosk\") mode";
@@ -115,6 +148,37 @@ SUMMARY
OPTIONS" in
Arg.parse argspec anon_fun usage_msg;
+ (* Read the init file. *)
+ let try_to_read_init_file filename =
+ let config = read_config_file filename in
+ List.iter (
+ function
+ | _, "display", mode -> display_mode := display_of_cli mode
+ | _, "delay", secs -> set_delay (float_of_string secs)
+ | _, "hist-cpu", secs -> historical_cpu_delay := int_of_string secs
+ | _, "iterations", n -> iterations := int_of_string n
+ | _, "sort", order -> set_sort order
+ | _, "connect", uri -> set_uri uri
+ | _, "debug", filename -> debug_file := filename
+ | _, "csv", filename -> set_csv filename
+ | _, "batch", b -> batch_mode := bool_of_string b
+ | _, "secure", b -> secure_mode := bool_of_string b
+ | _, "overwrite-init-file", "false" -> no_init_file ()
+ | lineno, key, _ ->
+ eprintf "%s:%d: configuration item ``%s'' ignored\n%!"
+ filename lineno key
+ ) config
+ in
+ (match !init_file with
+ | NoInitFile -> ()
+ | DefaultInitFile ->
+ let home = try Sys.getenv "HOME" with Not_found -> "/" in
+ let filename = home // ".virt-toprc" in
+ try_to_read_init_file filename
+ | InitFile filename ->
+ try_to_read_init_file filename
+ );
+
(* Connect to the hypervisor before going into curses mode, since
* this is the most likely thing to fail.
*)
@@ -1187,6 +1251,7 @@ and get_key_press state =
else if k = Char.code '1' then toggle_pcpu_display ()
else if k = Char.code '2' then toggle_net_display ()
else if k = Char.code '3' then toggle_block_display ()
+ else if k = Char.code 'W' then write_init_file ()
else unknown_command k
)
@@ -1327,6 +1392,77 @@ and toggle_block_display () = (* key 3 *)
| TaskDisplay | NetDisplay -> BlockDisplay
| BlockDisplay -> TaskDisplay
+(* Write an init file. *)
+and write_init_file () =
+ match !init_file with
+ | NoInitFile -> () (* Do nothing if --no-init-file *)
+ | DefaultInitFile ->
+ let home = try Sys.getenv "HOME" with Not_found -> "/" in
+ let filename = home // ".virt-toprc" in
+ _write_init_file filename
+ | InitFile filename ->
+ _write_init_file filename
+
+and _write_init_file filename =
+ try
+ (* Create the new file as filename.new. *)
+ let chan = open_out (filename ^ ".new") in
+
+ let time = Unix.gettimeofday () in
+ let tm = Unix.localtime time in
+ let printable_date_time =
+ sprintf "%04d-%02d-%02d %02d:%02d:%02d"
+ (tm.Unix.tm_year + 1900) (tm.Unix.tm_mon+1) tm.Unix.tm_mday
+ tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec in
+ let username =
+ try
+ let uid = Unix.geteuid () in
+ (Unix.getpwuid uid).Unix.pw_name
+ with
+ Not_found -> "unknown" in
+
+ let fp = fprintf in
+ let nl () = fp chan "\n" in
+ fp chan "# .virt-toprc virt-top configuration file\n";
+ fp chan "# generated on %s by %s\n" printable_date_time username;
+ nl ();
+ fp chan "display %s\n" (cli_of_display !display_mode);
+ fp chan "delay %g\n" (float !delay /. 1000.);
+ fp chan "hist-cpu %d\n" !historical_cpu_delay;
+ if !iterations <> -1 then fp chan "iterations %d\n" !iterations;
+ fp chan "sort %s\n" (cli_of_sort_order !sort_order);
+ (match !uri with
+ | None -> ()
+ | Some uri -> fp chan "connect %s\n" uri
+ );
+ if !batch_mode = true then fp chan "batch true\n";
+ if !secure_mode = true then fp chan "secure true\n";
+ nl ();
+ fp chan "# To send debug and error messages to a file, uncomment next line\n";
+ fp chan "#debug virt-top.out\n";
+ nl ();
+ fp chan "# Enable CSV output to the named file\n";
+ fp chan "#csv virt-top.csv\n";
+ nl ();
+ fp chan "# To protect this file from being overwritten, uncomment next line\n";
+ fp chan "#overwrite-init-file false\n";
+
+ close_out chan;
+
+ (* If the file exists, rename it as filename.old. *)
+ (try Unix.rename filename (filename ^ ".old")
+ with Unix.Unix_error _ -> ());
+
+ (* Rename filename.new to filename. *)
+ Unix.rename (filename ^ ".new") filename;
+
+ print_msg (sprintf "Wrote settings to %s" filename); sleep 2
+ with
+ | Sys_error err -> print_msg "Error: %s"; sleep 2
+ | Unix.Unix_error (err, fn, str) ->
+ print_msg (sprintf "Error: %s %s %s" (Unix.error_message err) fn str);
+ sleep 2
+
and show_help (_, _, _, hostname,
(libvirt_major, libvirt_minor, libvirt_release)) =
clear ();
diff --git a/virt-top/virt_top_utils.ml b/virt-top/virt_top_utils.ml
new file mode 100644
index 0000000..3a0c4ba
--- /dev/null
+++ b/virt-top/virt_top_utils.ml
@@ -0,0 +1,74 @@
+(* 'top'-like tool for libvirt domains.
+ * $Id: virt_top.ml,v 1.5 2007/08/30 13:52:40 rjones Exp $
+ *)
+
+let (//) = Filename.concat
+
+(* Input a whole file as a list of lines. *)
+let input_all_lines chan =
+ let lines = ref [] in
+ (try
+ while true; do
+ lines := input_line chan :: !lines
+ done
+ with
+ End_of_file -> ());
+ List.rev !lines
+
+(* Trim whitespace from the beginning and end of strings. *)
+let isspace c =
+ c = ' '
+ (* || c = '\f' *) || c = '\n' || c = '\r' || c = '\t' (* || c = '\v' *)
+
+let triml ?(test = isspace) str =
+ let i = ref 0 in
+ let n = ref (String.length str) in
+ while !n > 0 && test str.[!i]; do
+ decr n;
+ incr i
+ done;
+ if !i = 0 then str
+ else String.sub str !i !n
+
+let trimr ?(test = isspace) str =
+ let n = ref (String.length str) in
+ while !n > 0 && test str.[!n-1]; do
+ decr n
+ done;
+ if !n = String.length str then str
+ else String.sub str 0 !n
+
+let trim ?(test = isspace) str =
+ trimr (triml str)
+
+(* Read a configuration file as a list of (key, value) pairs.
+ * If the config file is missing this returns an empty list.
+ *)
+let blanks_and_comments = Str.regexp "^[ \t]*\\(#.*\\)?$"
+
+let read_config_file filename =
+ let lines =
+ try
+ let chan = open_in filename in
+ let lines = input_all_lines chan in
+ close_in chan;
+ lines
+ with
+ Sys_error _ -> [] in (* Ignore errors opening file. *)
+
+ (* Line numbers. *)
+ let lines =
+ let i = ref 0 in List.map (fun line -> (incr i; !i), line) lines in
+
+ (* Remove blank lines and comment lines. *)
+ let lines =
+ List.filter
+ (fun (lineno, line) ->
+ not (Str.string_match blanks_and_comments line 0)) lines in
+
+ (* Convert to key, value pairs. *)
+ List.map (
+ fun (lineno, line) ->
+ let key, value = ExtString.String.split line " " in
+ lineno, trim key, trim value
+ ) lines