From: goose
Subject: Parsing a configuration file; critics?
Date: 
Message-ID: <1126687038.558090.155040@g49g2000cwa.googlegroups.com>
Hello again everyone

Having been able to sneak an hour in between work and studying,
I wrote the following functions to help parse and store
configuration files. I suspect that this will be helpful
for any applications I write in the future.

I dont get any unexpected errors, but I am still a little
worried that I did not do everythnig kosher. Please criticise
and enlighten me where applicable.

Thanks in advance
L. K. Manickum

---------------------start test.lisp-------------------------------
;;;; read options from a file, in the form of
;;;; option=value
;;;;   OR
;;;; option = value
;;;;
;;;; A comment is from the first hash on a line to the end of line
;;;; Spaces are allowed in option or value strings as long as
;;;; the string does not end with a whitespace.
;;;;
;;;; TODO: must figure out how to use string-right-trim
;;;;       to trim all whitespace (tabs, etc).


;;;; all the options will get stored here
(defparameter *options* (make-hash-table))


(defun strip-comments (line)
"This function will return the given line sans comments.
 Make sure that line is not nil before calling."
  (setf line (subseq line 0 (position #\# line))))

(defun get-option (line)
"This function extracts the option from the given line."
  (if (position #\= line)
      (string-right-trim " "
                         (subseq line 0 (position #\= line)))
    (if (eq 0 (length line))
        nil
      line)))


(defun get-value (line)
"This function extracts the value from the given line."
  (if (null (position #\= line))
      nil
    (string-right-trim " "
                       (subseq line
                               (+ 1 (position #\= line))
                               (length line)))))

(defun store-option (option-value)
"This function stores the option and the value (if any)
 that is found on a single line into the hash table *options*."
  (unless (null option-value)
    (let ((line (strip-comments option-value)))
      (setf option (get-option line))
      (setf value (get-value line))
      (unless (null option)
        (setf (gethash (intern option) *options*) value)))))

(defun read-config (filename)
"This function opens and reads in a file containing the
 option/value pairs. It use store-option (above) to store
 the option/value pairs in a has table."
  (with-open-file (in filename
                      :direction    :input
                      :if-does-not-exist nil)
                  (when (streamp in)
                    (with-standard-io-syntax
                     (let ((line nil))
                       (do ((ret-val
                             t
                             (multiple-value-bind (tline result)
                                                  (read-line in nil)
                                                  (setf ret-val result)
                                                  (setf line tline))))
                           ((null ret-val))
                           (store-option line))
                       (print "*** done ***"))))))

;;;; we test the functions
(if (null (read-config "config.rc"))
    (print "cannot read file")
  (progn
    (print *options*)
    (print (gethash (intern "name3") *options*))))

----------------------end test.lisp-------------------------------

---------------------start config.rc-------------------------------
# test that the comment goes away
name1=value1      # test a comment after significant data
name2 = value2    # test spaces getting trimed
name3=value3
# test that a blank line does not throw up

name4 value4      # test an option (with spaces) without value
name5 name6 = value5 value6   # test spaces in options and values
name3 = value7    # test duplicate entries

# End of config.rc

----------------------end config.rc-------------------------------

From: Rob Thorpe
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <1126694444.861871.106850@g43g2000cwa.googlegroups.com>
goose wrote:
> Hello again everyone
>
> Having been able to sneak an hour in between work and studying,
> I wrote the following functions to help parse and store
> configuration files. I suspect that this will be helpful
> for any applications I write in the future.
>
> I dont get any unexpected errors, but I am still a little
> worried that I did not do everythnig kosher. Please criticise
> and enlighten me where applicable.
>
> Thanks in advance
> L. K. Manickum
>
> ---------------------start test.lisp-------------------------------
> ;;;; read options from a file, in the form of
> ;;;; option=value
> ;;;;   OR
> ;;;; option = value
> ;;;;
> ;;;; A comment is from the first hash on a line to the end of line
> ;;;; Spaces are allowed in option or value strings as long as
> ;;;; the string does not end with a whitespace.
> ;;;;
> ;;;; TODO: must figure out how to use string-right-trim
> ;;;;       to trim all whitespace (tabs, etc).

You need only do this if you have a set of configuration files of this
form that need parsing.  If you're creating a configuration file format
for a new program then it can be simpler to format the configuration as
a lists.  That way read and print can be used to handle them.

You also have to check the data is correct afterwards of-course, and
probably break it down into smaller lists.  But it's a more general way
than parsing key-value pairs.
From: Pascal Bourguignon
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <874q8odmoe.fsf@thalassa.informatimago.com>
"goose" <····@webmail.co.za> writes:
> Having been able to sneak an hour in between work and studying,
> I wrote the following functions to help parse and store
> configuration files. I suspect that this will be helpful
> for any applications I write in the future.
> [...]
> ---------------------start config.rc-------------------------------
> # test that the comment goes away
> name1=value1      # test a comment after significant data
> name2 = value2    # test spaces getting trimed
> name3=value3
> # test that a blank line does not throw up
>
> name4 value4      # test an option (with spaces) without value
> name5 name6 = value5 value6   # test spaces in options and values
> name3 = value7    # test duplicate entries
>
> # End of config.rc
>
> ----------------------end config.rc-------------------------------

What's wrong with:

---------------------start config.rc-------------------------------
;; test that the comment goes away
(name1 value1)      ;; test a comment after significant data
(name2    value2)   ;; test spaces getting trimed
(name3 value3)
;; test that a blank line does not throw up

(name4 value4)      ;; test an option (with spaces) without value
(name5\ name6  value5\ value6)   ;; test spaces in options and values
(name3  value7)    ;; test duplicate entries

;; End of config.rc
----------------------end config.rc-------------------------------

Then you'd only have to do:

(defparameter *config* (with-open-file (conf "config.rc")
                          (loop for item = (read conf nil conf)
                                until (eq item conf)
                                collect item)))





Actually, for my configurations I'm even lazier:

---------------------start config.rc-------------------------------
;; test that the comment goes away
(
(name1 value1)      ;; test a comment after significant data
(name2    value2)   ;; test spaces getting trimed
(name3 value3)
;; test that a blank line does not throw up

(name4 value4)      ;; test an option (with spaces) without value
(name5\ name6  value5\ value6)   ;; test spaces in options and values
(name3  value7)    ;; test duplicate entries
)
;; End of config.rc
----------------------end config.rc-------------------------------

(defparameter *config* (with-open-file (conf "config.rc") (read conf)))


And sometimes, I'm even laziest:

(defstruct config name1 name2 name3 name4 |NAME5 NAME6|)
(defparameter *config* (make-config :name1 'value1
                                    :name2 'value2
                                    :name3 'value3
                                    :name4 'value4
                                    :|NAME5 NAME6| 'value5\ value6))
(with-open-file (conf "config.rc" :direction :output)
    (print *config* conf))

---------------------start config.rc-------------------------------
#S(CONFIG
   :NAME1 VALUE1
   :NAME2 VALUE2
   :NAME3 VALUE3
   :NAME4 VALUE4
   :|NAME5 NAME6| |VALUE5 VALUE6|)
----------------------end config.rc-------------------------------

(defparameter *config* (with-open-file (conf "config.rc") (read conf)))

... (config-name1 *config*) ...

                                    
-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
Small brave carnivores
Kill pine cones and mosquitoes
Fear vacuum cleaner
From: goose
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <1126691819.592283.277730@g14g2000cwa.googlegroups.com>
Pascal Bourguignon wrote:
> "goose" <····@webmail.co.za> writes:
> > Having been able to sneak an hour in between work and studying,
> > I wrote the following functions to help parse and store
> > configuration files. I suspect that this will be helpful
> > for any applications I write in the future.

<snipped>

> What's wrong with:
>
> ---------------------start config.rc-------------------------------
> ;; test that the comment goes away
> (name1 value1)      ;; test a comment after significant data
> (name2    value2)   ;; test spaces getting trimed
> (name3 value3)
> ;; test that a blank line does not throw up
>
> (name4 value4)      ;; test an option (with spaces) without value
> (name5\ name6  value5\ value6)   ;; test spaces in options and values
> (name3  value7)    ;; test duplicate entries
>
> ;; End of config.rc
> ----------------------end config.rc-------------------------------

Unfortunately, my system is not completely lisp, and
the configuration files are written by a different program.

/etc/hosts, for example, is of a format that only slightly
differs to the one I describe above; /etc/lilo.conf is
almost exactly the same as the one I described above.

Other files which are almost the same *or* exactly the same
format in my system are:
   /etc/ftpusers
   /etc/squid.conf
   /etc/services

There are a few more, I just got tired of looking.

Rather happily, I dont feel the need to rewrite all these applications
in lisp just to take advantage of a new file format, so for now
I will stick to reading in (at the very least) configuration files
in a format that is similar enough to other files that users have
become used to editing.

<snipped>

L. K. Manickum
From: Frank Buss
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <n6crrbo68lxv$.1w6365jgo5nvp.dlg@40tude.net>
goose wrote:

> I dont get any unexpected errors, but I am still a little
> worried that I did not do everythnig kosher. 

you are right to worry, because there are some warnings when compiling it
(at least with Lispworks), which gives you hints, that something could be
wrong.

> (defun strip-comments (line)
> "This function will return the given line sans comments.
>  Make sure that line is not nil before calling."
>   (setf line (subseq line 0 (position #\# line))))

it doesn't make sense to set the local "line" variable, but it works,
because setf returns the value, too. You should write it like this:

(defun strip-comments (line)
  (subseq line 0 (position #\# line)))

> (defun store-option (option-value)
> "This function stores the option and the value (if any)
>  that is found on a single line into the hash table *options*."
>   (unless (null option-value)
>     (let ((line (strip-comments option-value)))
>       (setf option (get-option line))
>       (setf value (get-value line))
>       (unless (null option)
>         (setf (gethash (intern option) *options*) value)))))

"option" and "value" are not defined and assumed special.

Why do you use "intern"? This could cause problems, when you are using this
reader from another package. If you are using "equal" as the test parameter
for your hashtable, strings are fine. 

(defun store-option (option-value)
  (when option-value
    (let* ((line (strip-comments option-value))
           (option (get-option line))
           (value (get-value line)))
      (when option
        (setf (gethash option *options*) value)))))

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: goose
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <1126697029.676970.72090@z14g2000cwz.googlegroups.com>
Frank Buss wrote:
> goose wrote:
>
> > I dont get any unexpected errors, but I am still a little
> > worried that I did not do everythnig kosher.
>
> you are right to worry, because there are some warnings when compiling it
> (at least with Lispworks), which gives you hints, that something could be
> wrong.

Fair enough, does anyone here know how I can turn up the diagnostics
on gnu clisp?

A quick reading of the clisp --help output gives no clue, and there
was no man page or info docs installed with it.

Ideally, I'd like to turn the diagnostics high enough to give
a (metaphorical) nosebleed.

>
> > (defun strip-comments (line)
> > "This function will return the given line sans comments.
> >  Make sure that line is not nil before calling."
> >   (setf line (subseq line 0 (position #\# line))))
>
> it doesn't make sense to set the local "line" variable, but it works,
> because setf returns the value, too. You should write it like this:
>
> (defun strip-comments (line)
>   (subseq line 0 (position #\# line)))

Point taken! I should've realised this.

>
> > (defun store-option (option-value)
> > "This function stores the option and the value (if any)
> >  that is found on a single line into the hash table *options*."
> >   (unless (null option-value)
> >     (let ((line (strip-comments option-value)))
> >       (setf option (get-option line))
> >       (setf value (get-value line))
> >       (unless (null option)
> >         (setf (gethash (intern option) *options*) value)))))
>
> "option" and "value" are not defined and assumed special.
>
> Why do you use "intern"? This could cause problems, when you are using this
> reader from another package. If you are using "equal" as the test parameter
> for your hashtable, strings are fine.
>
> (defun store-option (option-value)
>   (when option-value
>     (let* ((line (strip-comments option-value))
>            (option (get-option line))
>            (value (get-value line)))
>       (when option
>         (setf (gethash option *options*) value)))))

Once again, I see the point; I never knew about using ":test 'equal"
(had to look it up now, finally decided to download the Hypersec to
my machine), but I find that using ":test 'equal" works properly.

I originally used (intern ...) because I found that my hash table
was not finding the correct string (option-string) and was always
returning nil; I reasoned that perhaps strings could not be
in the hash table (of course, now I know better :-).

I was also short-sighted in not using (let* ...) to bind the
variables which I will be using; I have fixed my function
as per critique.

One last question, what exactly *is* the difference between
eq, equal, equalp, etc?

Thanks for the critique,
L. K. Manickum
From: Joe Marshall
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <hdcnenev.fsf@alum.mit.edu>
"goose" <····@webmail.co.za> writes:

> One last question, what exactly *is* the difference between
> eq, equal, equalp, etc?

This isn't *quite* exact, but should help.

EQ     tests `pointer equality'.  Two things are EQ iff they have the
       exact same address *OR* if they are small non-pointer objects
       with the exact same bit pattern.  It can give you unexpected
       answers on things such as numbers:

         (eq 4 4) => T
         (eq 4000000000 4000000000) => NIL

       and this last answer might differ if I compiled the code.


EQL    tests pointer equality but does the right thing with regard to
       small non-pointer objects.  It is like EQ without the screw cases.

       (eql 4000000000 4000000000) => T
 
       But note that EQL still discriminates between objects of
       different types:

       (eql 4 4.0d0) => NIL

       or different case:

       (eql #\a #\A) => nil


EQUAL   tests `list and string similarity'.  Informally, two lists are
        EQUAL if they print the same way.  Two strings are EQUAL if
        they have the same characters and the same case.  Other
        objects, like numbers and arrays are compared using EQL.
        EQUAL has to traverse the list structure it is comparing, so
        it is slower than EQL.

        (equal (list 1.0d0 "foobar" 'a) (list 1.0d0 "foobar" 'a)) => T

        (equal (list 1.0d0 "foobar" 'a) (list 1 "foobar" 'a)) => NIL

        (equal (list "foobar" 'a) (list "Foobar" 'a)) => NIL


EQUALP  tests `loose object similarity'.  Case is ignored in strings,
        type is ignored in numbers, arrays are equalp if they have
        equalp contents, etc.

        (equalp #(1.0d0 "foobar" a) #(3/3 "FooBar" a)) => T
From: Peter Seibel
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <m21x3rplzq.fsf@gigamonkeys.com>
Joe Marshall <·········@alum.mit.edu> writes:

> "goose" <····@webmail.co.za> writes:
>
>> One last question, what exactly *is* the difference between
>> eq, equal, equalp, etc?
>
> This isn't *quite* exact, but should help.
>
> EQ     tests `pointer equality'.  Two things are EQ iff they have the
>        exact same address *OR* if they are small non-pointer objects
>        with the exact same bit pattern.  It can give you unexpected
>        answers on things such as numbers:
>
>          (eq 4 4) => T
>          (eq 4000000000 4000000000) => NIL
>
>        and this last answer might differ if I compiled the code.

And it wouldn't be non-conformant for (eq 4 4) to return NIL. Also, my
understanding is that:

  (let ((x 4000000000)) ;; or 4
    (eq x x))

can also evaluate to either T or NIL.

> EQL    tests pointer equality but does the right thing with regard to
>        small non-pointer objects.  It is like EQ without the screw cases.

Another way to think about it is that EQL tests for "object identity"
in a world where numbers and characters are first-class objects and
there is only one object for each mathematical number of a given
representation type. (EQ, in this world, peeks under the covers of the
implemenation in an unseemly way, ruining the illusion that numbers
and characters are first-class objects with identity.)

>        (eql 4000000000 4000000000) => T
>  
>        But note that EQL still discriminates between objects of
>        different types:
>
>        (eql 4 4.0d0) => NIL
>
>        or different case:
>
>        (eql #\a #\A) => nil

Because those are "different" objects.

> EQUAL   tests `list and string similarity'.  Informally, two lists are
>         EQUAL if they print the same way.  Two strings are EQUAL if
>         they have the same characters and the same case.  Other
>         objects, like numbers and arrays are compared using EQL.
>         EQUAL has to traverse the list structure it is comparing, so
>         it is slower than EQL.
>
>         (equal (list 1.0d0 "foobar" 'a) (list 1.0d0 "foobar" 'a)) => T
>
>         (equal (list 1.0d0 "foobar" 'a) (list 1 "foobar" 'a)) => NIL
>
>         (equal (list "foobar" 'a) (list "Foobar" 'a)) => NIL
>
>
> EQUALP  tests `loose object similarity'.  Case is ignored in strings,
>         type is ignored in numbers, arrays are equalp if they have
>         equalp contents, etc.
>
>         (equalp #(1.0d0 "foobar" a) #(3/3 "FooBar" a)) => T

And since Kent Pitman doesn't seem to have chimed in yet, I'll save
him the trouble and point out that the later two, EQUAL and EQUALP are
essentially arbitrary equivalence predicates. If they do what you want
in some situation, great; but if not, don't sweat it; just write your
own equivalence predicate that loosens things up in the ways that your
application demands.

-Peter

-- 
Peter Seibel           * ·····@gigamonkeys.com
Gigamonkeys Consulting * http://www.gigamonkeys.com/
Practical Common Lisp  * http://www.gigamonkeys.com/book/
From: Joe Marshall
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <irx2cl5e.fsf@alum.mit.edu>
Peter Seibel <·····@gigamonkeys.com> writes:

> And it wouldn't be non-conformant for (eq 4 4) to return NIL. 

It wouldn't be non-conformant, but it would be slightly unusual.  Most
(but not all!) implementations have an immediate representation for
small numbers.

> Also, my
> understanding is that:
>
>   (let ((x 4000000000)) ;; or 4
>     (eq x x))
>
> can also evaluate to either T or NIL.

Yep.

EQ should probably be avoided altogether in favor of EQL, but I'm still
in the habit of using EQ when I *know* that numbers are not going to be
used.
From: Jeff M.
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <1126813277.954365.35110@o13g2000cwo.googlegroups.com>
Is there ever a [noticeable] performance hit for using one instead of
the other given a known circumstance? For example, if I know that I'm
comparing symbols or keywords, is EQL going to be slower than EQ all
the time or are each of the comparitors derived from each other:

(defun eql (a b)
  (if (eq a b) t #| do extra tests |#))

(defun equal (a b)
  (if (eql a b) t #| do extra tests |#))

(defune equalp (a b)
  (if (equal a b) t #| do extra tests |#))

Also, when dealing with symbols across packages can you get into
trouble using EQUAL or EQUALP since they test "they print the same"?

Jeff M.
From: Joe Marshall
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <k6hhzmxp.fsf@alum.mit.edu>
"Jeff M." <·······@gmail.com> writes:

> Is there ever a [noticeable] performance hit for using one instead of
> the other given a known circumstance? For example, if I know that I'm
> comparing symbols or keywords, is EQL going to be slower than EQ all
> the time or are each of the comparitors derived from each other:
>
> (defun eql (a b)
>   (if (eq a b) t #| do extra tests |#))
>
> (defun equal (a b)
>   (if (eql a b) t #| do extra tests |#))
>
> (defune equalp (a b)
>   (if (equal a b) t #| do extra tests |#))

It depends on the implementation, of course, but most of them do a
quick EQ test first, and if that fails, go on to some more tests.
The extra tests for EQL are pretty minimal.

> Also, when dealing with symbols across packages can you get into
> trouble using EQUAL or EQUALP since they test "they print the same"?

Maybe I shouldn't have said that.  Neither EQUAL nor EQUALP actually
test the behavior of the printer.  If symbol A and symbol B are not
EQ, then they are not EQUAL nor are they EQUALP.
From: Frank Buss
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <16maua3bjzn15$.9kv6eodhabpw.dlg@40tude.net>
goose wrote:
| 
> Fair enough, does anyone here know how I can turn up the diagnostics
> on gnu clisp?

I don't know, I'm using one of those commercial Lisp.

> One last question, what exactly *is* the difference between
> eq, equal, equalp, etc?

You'll find this in the hyperspec, but to quote a more understandable post
of c.l.l.:

David Sletten wrote:

| All of the equality predicates can be tricky at first, but they're not 
| so bad if you keep a few things in mind. First, there are type-specific 
| and generic predicates. The generic predicates essentially become more 
| liberal as their names get longer: EQ -> EQL -> EQUAL -> EQUALP 

| EQ tests for identical objects in memory. This is the simplest (and most 
| efficient) test, but it usually isn't what you want. Since symbols have 
| a unique representation in a package testing whether two symbols are 
| equal, i.e., whether two things refer to the identical symbol, is 
| usually where you would use EQ. 

| EQL is similar to EQ, but it is guaranteed to return T for characters, 
| floats, and bignums, which may have distinct representations. EQL is 
| also the default equality predicate for most Lisp operators such as 
| MEMBER, ASSOC, etc... 

| EQUAL basically says that things which print the same are equal. And 
| EQUALP extends EQUAL to disregard case-sensitivity and different numeric 
| types: 
| (eq '(a b c) '(a b c)) => NIL 
| (equal '(a b c) '(a b c)) => T 
| (equal 1 1.0) => NIL 
| (equalp 1 1.0) => T 
| (equal #\a #\A) => NIL 
| (equalp #\a #\A) => T 

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Pascal Bourguignon
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <87k6hjc0a4.fsf@thalassa.informatimago.com>
"goose" <····@webmail.co.za> writes:
> Fair enough, does anyone here know how I can turn up the diagnostics
> on gnu clisp?

You get more error messages when you compile the functions and the files.

[38]> (defun store-option (option-value)
"This function stores the option and the value (if any)
 that is found on a single line into the hash table *options*."
  (unless (null option-value)
    (let ((line (strip-comments option-value)))
      (setf option (get-option line))
      (setf value (get-value line))
      (unless (null option)
        (setf (gethash (intern option) *options*) value)))))
STORE-OPTION
[39]> (compile 'store-option)
WARNING in STORE-OPTION :
OPTION is neither declared nor bound,
it will be treated as if it were declared SPECIAL.
WARNING in STORE-OPTION :
VALUE is neither declared nor bound,
it will be treated as if it were declared SPECIAL.
WARNING in STORE-OPTION :
OPTION is neither declared nor bound,
it will be treated as if it were declared SPECIAL.
WARNING in STORE-OPTION :
OPTION is neither declared nor bound,
it will be treated as if it were declared SPECIAL.
WARNING in STORE-OPTION :
*OPTIONS* is neither declared nor bound,
it will be treated as if it were declared SPECIAL.
WARNING in STORE-OPTION :
VALUE is neither declared nor bound,
it will be treated as if it were declared SPECIAL.
STORE-OPTION ;
6 ;
6
[40]> 

> A quick reading of the clisp --help output gives no clue, and there
> was no man page or info docs installed with it.
>
> Ideally, I'd like to turn the diagnostics high enough to give
> a (metaphorical) nosebleed.

>> (defun strip-comments (line)
>>   (subseq line 0 (position #\# line)))
>
> Point taken! I should've realised this.

In general, you could write all this in a more functionnal style.


(defun strip-comments (line)
  "This function will return the given line sans comments.
 Make sure that line is not nil before calling."
  (subseq line 0 (position #\# line)))

;; merge get-option and get-value since they need the same position.
(defun parse-option-line (line)
  "RETURN: option and value as two values."
  (unless (null line)
    (let* ((stripped  (strip-comments line))
           (=pos      (position #\= stripped))
           (option    (and =pos (string-trim " " (subseq stripped 0 =pos)))))
      (when option
        (values option  (string-trim " " (subseq stripped (1+ =pos))))))))

;; make store-option setf-able:
(defun (setf get-option) (value conf option)
  (setf (gethash (string option) conf) value))

;; abstract get-option to hide use of gethash, so you can change
;; the implementation of the configuration databases.
(defun get-option (conf option)
  (gethash (string option) conf))

;; make read-config a pure function.
(defun read-config (filename &key (case-sensitive-p nil))
  "This function opens and reads in a file containing the
 option/value pairs. It use store-option (above) to store
 the option/value pairs in a has table."
  (with-open-file (in filename
                      :direction :input
                      :if-does-not-exist nil)
    (when in ; read-line doesn't need standard io syntax.
      (let ((conf (make-hash-table :test (if case-sensitive-p
                                             (function equal)
                                             (function equalp)))))
          (do ((line (read-line in nil nil) (read-line in nil nil)))
              ((null line) conf)
            (multiple-value-bind (option value) (parse-option-line line)
              (when option
                (setf (get-option conf option) value))))))))


;;;; we test the functions
(let ((conf (read-config "/tmp/config.rc")))
  (if conf
      (progn
        (print conf)
        (print (get-option conf "name3"))
        (print (get-option conf :name3)))
      (print "could not read the file, or it was empty.")))

#S(HASH-TABLE :TEST EQUALP ("name5 name6" . "value5 value6")
   ("name3" . "value7") ("name2" . "value2") ("name1" . "value1")) 
"value7" 
"value7" 
"value7"


So now you can write more programs, such as:

  (diff-config (read-config "/etc/myconf")
               (read-config "/etc/myconf.old"))


> One last question, what exactly *is* the difference between
> eq, equal, equalp, etc?

Better read CLHS and http://www.nhplace.com/kent/PS/EQUAL.html

For strings, EQUAL = STRING=  and  EQUALP = STRING-EQUAL


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

The world will now reboot.  don't bother saving your artefacts.
From: goose
Subject: Re: Parsing a configuration file; critics?
Date: 
Message-ID: <1126790849.075016.303270@g47g2000cwa.googlegroups.com>
goose wrote:
> Frank Buss wrote:
> > goose wrote:
> >

<snipped all the help>

Firstly, even though I am replying to a post by Frank
Buss, I'd like to thank everyone else who replied to
me; I'm certainly indebted to you all for the time
you are taking to help me.

Now, on to the topic at hand:
-----------------
1. I've changed my code a little (including following most
   of the suggestions that i've received). The biggest change
   is that I've decided that my function read-config (which
   used to use a global variable to store the hash-table) can
   instead return a hash-table so that the caller code which
   used to be this:

      (defparameter *options* (make-hash-table :test 'equal))
      ...
      (read-config "config.rc")

   can be changed to this:

      (defparameter *options* (read-config "config.rc"))

   That eliminates some dependency for the function read-config
   (which used to need the variable *options* to be already
defparameter'd
    with a hash-table).

   I think that this is better, but would welcome critiques on the
   method :-). If anyone would like to see the finished product (which
   isn't all that different from the postings here), say so and I'd
   gladly post it.

-----------------
2. In the spirit of learning, I've stolen (err, "borrowed") some
company
   time today to write another function. I must say that my development
   speed has increased tremendously (first function = 1 hour; 2nd
function
   20 mins :-).

   Any criticisms of my code would be welcomed; I've pasted the code at
   the end of this post.

-----------------
3. I'm a little worried about the state of lisp. There seems to be
   many incompatibilities between various lisp implementations. I'm
   from a (primarily) C background, and sorta assumed that there is
   an equivalent in all the lisp implementations of the C std (i.e.
   if I compile my project with "gcc -Wall -ansi -pedantic" then
   I *know* that that project will compile on all conforming
   hosted implementations).

   With lisp, the documentations for the various lisps dont have a
   "-ansi -pedantic" flag that I can use to make sure that my
   code is runnable on all common-lisp implementations.

   In the lisp world, each implementation seems to fnid a need to
   be different. Why? I tend to write my C code in perfectly portable
   modules (and wrap up platform dependencies in lower level modules
   compiled with different flags and with different compilers).

   If the lisp implementation could warn me that a certain function
   (or functionality) that I am using is non-standard while it runs,
   it would make it that much easier to remember what can be used with
   wild abandon and what should be wrapped up into a dependency
library.

   If I am off-base here, please, enlighten me; am I missing something
   or are my paragraphs above correct?


Once again, I would like to thank everyone for the efforts.

regards
L. K. Manickum



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;; FUNCTION: parse-long-opts
 ;;;; Parses gnu-style long options in c/line list *args*
 ;;;;
 ;;;; Long options are preceded with a double dash like this:
 ;;;;    --option
 ;;;; An option may have a value associated with it like this:
 ;;;;    --input=/etc/hosts
 ;;;;
 ;;;; Options may be repeated, but only the final one takes effect.
 ;;;;   --shoes=his --verbose --shoes=hers
 ;;;;  (shoes will take the value of "hers")
 ;;;;
 ;;;; An option of the first form will be stored in a hash table
 ;;;; with a hash of the option name and a value of t (not a string)
 ;;;;
 ;;;; An option of the second form will be stored in the hash table
 ;;;; with a value of the value given (string form); i.e. everything
 ;;;; after the equals sign.
 ;;;;
 ;;;; Everything after "-- " is ignored.
 ;;;;


(defun parse-long-opts (args)
  "Parse and store the c/line arguments in the list "args" in a hash
  table which gets returned."
  (unless (null args)
    (let ((hash-table (make-hash-table :test 'equal)))
      (dolist (option-value args)
        (let* ((dashes (subseq option-value 0 2))
               (pos-delim (position #\= option-value))
               (option (subseq option-value 2
                               (if pos-delim
                                   pos-delim
                                 (length option-value)))))
          (when (and (equal dashes "--") (eq (length option) 0))
            (return hash-table))
          (when (and option (equal dashes "--"))
            (let ((value (if pos-delim
                             (subseq option-value
                                     (+ 1 pos-delim)
                                     (length option-value))
                           t)))
              (setf (gethash option hash-table) value)))))
      hash-table)))

 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;; test the function parse-opts
(defparameter *c-line-options* (parse-long-opts *args*))

;;;; this program was called with the arguments:
;;;; --help --difficulty=4 this-is-not-an-option --
--ghost-option=should-not-appear-in-table

(if (null *c-line-options*)
    (print "no options given?")
  (progn
    (print *args*)
    (print *c-line-options*)))

(when *c-line-options*
  (progn
    (if (gethash "help" *c-line-options*)
        (print "Help requested!!!")
      (print "no help requested"))
    (let ((difficulty (gethash "difficulty" *c-line-options*)))
      (cond
       ((equal difficulty "1") (print "rookie"))
       ((equal difficulty "2") (print "intermediate"))
       ((equal difficulty "3") (print "advanced"))
       ((equal difficulty "4") (print "insane"))
       ((equal difficulty "5") (print "just plain stupid"))
       ((equal nil difficulty) (print "no difficulty selected"))
       (t (print "unknown difficulty level selected"))))))