Author Topic: AutoLISP Config Class  (Read 7042 times)

0 Members and 1 Guest are viewing this topic.

Xander

  • Guest
AutoLISP Config Class
« on: March 28, 2011, 11:30:18 PM »
Unfortunately, the dreaded time has come where I've been tasked with updating my companies...<insert profanity> routines. Unfortunately for me these were written around the time LISP was integrated into AutoCAD and they are bad... The first thing I was looking at doing before losing my mind was integrating a basic config writing class to read a file in the format of:

Quote
#Typical comment identifier
[SECTION]
KEY=VALUE

Below is my first run which provides the basic functionality I'm looking at with more to add. I've avoided Visual-LISP usage as I'm hoping to allow usage of this throughout all platform releases of AutoCAD (including OSX).

Could any LISP GODS look at this and offer comment? I'm more of a C#/C++ guy.

Code: [Select]
;|
**=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=**
**CONFIG INI READING/WRITING CLASS                                                                                                   **
**         NOTES:                                                                                                                    **
**         -THERE IS CURRENTLY NO WAY TO CREATE A KEY AND VALUE FROM SCRATCH                                                         **
**         -THERE IS NO FILE GENERATION FROM SCRATCHHAT THE EXACT PROBLEM IS?                                                        **
**=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=**
**About                                                                                                                              **
**         The need for this class derived from the necessity of requiring an ability to update the company standards throughout     **
**         all our companies routines, where shamefully, everything was hardcoded.                                                   **
**         Currently there is only basic functionality but I'm hoping to have a complete read/write/append/create solution           **
**=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=**
**Usage                                                                                                                              **
**         (xreadconfig "SECTION" "KEY") will return the value of:                                                                   **
**                  [SECTION]                                                                                                        **
**                  KEY=VALUE                                                                                                        **
**         (xwriteconfig "SECTION" "KEY" "VALUE") will overwrite the existing key with the new specified value                       **
**=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=**
**Revision                                                                                                                           **
**         A. Initial Feedback Release                                                                                    29/03/2011 **
**                                                                                                                                   **
**=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=**
|;

;;;Iterate through a config file looking for a particular value
(DEFUN xreadconfig ($section $value)
  (SETQ $configfile (OPEN "C:/SOME/FILEPATH.ini" "r"))             ;Open the config file for read
  (SETQ $configsection (STRCAT "[" $section "]"))     ;Append the opening and closing brackets to the required sections
  (SETQ $currentline (READ-LINE $configfile))     ;Assign a throw-away variable
  (SETQ $returnvalue nil)     ;Create a value to return, nil if empty

  (SETQ $linescan 1)     ;Set a loop variable
  (WHILE (= $linescan 1)     ;While the loop variable is still 1
    (PROGN
      (IF (/= $currentline $configsection)     ;Iterate through the config file looking for the section
(PROGN
  (SETQ $currentline (READ-LINE $configfile))     ;Jump to the next line
  (IF (READ-LINE $configfile)     ;Check the file status
    (SETQ $currentline (READ-LINE $configfile))     ;Case True - There is still Data within the file
    (SETQ $linescan 0)     ;Case False - We have reached the End of File
  )
)
(PROGN
  (SETQ $linescan 0)
)
      )
    )
  )     ;End loop

  (IF (= $currentline $configsection)     ;If our current line is the section title
    (PROGN
      (SETQ loopt 1)     ;Set the loop to begin
      (WHILE (= loopt 1)     ;While loop is active
(SETQ $currentline (READ-LINE $configfile))
(IF (/= $currentline nil)     ;If the current line is not nil (empty)
  (PROGN     ;Do the following
    (SETQ $linecharacter (SUBSTR $currentline 1 1))     ;Obtain the first character in the string
    (COND     ;Select a matching case for the character
      ((= $linecharacter "[") (SETQ loopt 0))     ;If the starting character is an open bracket, we have a section marker!
      ((= $linecharacter "#"))     ;If the starting character is a hash, we have a comment!
      ((= $linecharacter nil))
      (T     ;If the case doesn't match any of the above, do the following
       (PROGN
(SETQ $configkey (xfindconfigkey $currentline))     ;Get the configline key
(IF (= $configkey $value)     ;If the key matches what we are looking for
   (PROGN     ;Do the following
     (SETQ $returnvalue     ;Calculate the return value (the remainder of the string after the '='
    (SUBSTR $currentline
    (+ (STRLEN $configkey) 2)
    (- (STRLEN $currentline) (STRLEN $configkey))
    )
     )
     (SETQ loopt 0)     ;Break the loop
   )     ;end case true
)     ;end if
       )
      )     ;end condtional case
    )     ;end conditional
  )     ;end progn
)     ;end if
      )
      ;;end while
    )
  )
  (CLOSE $configfile)
  (IF (/= nil $returnvalue)
    (PRINC $returnvalue)     ;Clean the return cache (removes any nil return value)
    (PRINC)
  )
)
    ; **=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=**
(DEFUN xwriteconfig ($section $key $value)
  (SETQ $configfile (OPEN "C:/SOME/FILEPATH.ini" "r"))             ;Open the config file for read
  (SETQ $configsection (STRCAT "[" $section "]"))     ;Append the opening and closing brackets to the required sections
  (SETQ $replacevalue nil)     ;Set the replace value to look for
  (SETQ $currentline (READ-LINE $configfile))     ;Assign a throw-away variable
  (SETQ $configcache nil)

  (WHILE $currentline     ;While there are still config lines to read
    (IF $currentline
      (SETQ $configcache (APPEND $configcache (LIST $currentline)))     ;Add the config lines to the cache
    )
    (IF $currentline
      (SETQ $currentline (READ-LINE $configfile))     ;Iterate to the next config line
    )
  )
  (CLOSE $configfile)

  (SETQ $sectionmarker 0)
  (FOREACH $line $configcache
    (IF (= $line $configsection)
      (SETQ $sectionmarker 1)
    )
    (IF (= $sectionmarker 1)
      (PROGN
(SETQ $configkey (xfindconfigkey $line))
(IF (= $configkey $key)
  (PROGN
    (SETQ $replacevalue $line)
    (SETQ $sectionmarker 0)
  )
)
      )
    )
  )

  (IF (/= nil $replacevalue)
    (PROGN
      ;;WRITE THE NEW CONFIG FILE
      (SETQ $configfile (OPEN "C:/SOME/FILEPATH.ini" "w"))
      (SETQ $linecount 0)
      (WHILE (NTH $linecount $configcache)
(IF (= (NTH $linecount $configcache) $replacevalue)
  (WRITE-LINE (STRCAT $key "=" $value) $configfile)
  (WRITE-LINE (NTH $linecount $configcache) $configfile)
)
(IF (NTH $linecount $configcache)
  (SETQ $linecount (1+ $linecount))
)
      )
      (CLOSE $configfile)
    )
  )
  (PRINC)     ;Clean the return cache (removes any nil return value)
)

;;;Get key value from string "KEY=VALUE"
(DEFUN xfindconfigkey ($value)
  (SETQ $iterator 1)     ;Create a counting iterator
  (SETQ $found 0)     ;Create an integer to identify the '=' position
  (SETQ $stringlength (STRLEN $value))     ;Get the string length
  (SETQ $returnvalue nil)     ;Set a return value
  (WHILE (< $iterator $stringlength)     ;While the iterator is currently less than the string length
    (IF (= (SUBSTR $value $iterator 1) "=")     ;Check if the first/next character is an '='
      (PROGN     ;Case True
(SETQ $found $iterator)     ;Set the position the '=' was found at
(SETQ $iterator $stringlength)     ;Set the set the iteration value to the string length to break the while loop
      )
    )
    (SETQ $iterator (+ $iterator 1))     ;Increate the iteration
  )
  (IF (/= $found 0)     ;If the found integer value is not 0
    (SETQ $returnvalue (SUBSTR $value 1 (- $found 1)))     ;Return the found value/key
    (SETQ $returnvalue nil)     ;Else, return null
  )
)

Also, does anyone know if 'DEFSTRUCT' was added into the core AutoLISP??

Regards,
Xander

hermanm

  • Guest
Re: AutoLISP Config Class
« Reply #1 on: March 29, 2011, 01:00:34 AM »
You may be interested in:

http://download.rhino3d.com/McNeel/1.0/doslib/

There is no DEFSTRUCT, but I did write a routine to create structure using AX datatypes.

Search this board for AXStruct if interested.

Lee Mac

  • Seagull
  • Posts: 12905
  • London, England
Re: AutoLISP Config Class
« Reply #2 on: March 29, 2011, 08:33:26 AM »
Hi Xander,

In my opinion, when using LISP, data can be manipulated a lot easier using lists. Hence I would use a function similar to this:

Code: [Select]
(defun XParseData ( file / line sub lst i m ) 
  (if (and (setq file (findfile file)) (setq file (open file "r")))
    (progn
      (while (setq line (read-line file))
        (cond
          ( (eq "[" (substr line 1 1))

            (if sub (setq lst (cons (reverse sub) lst)))
            (setq sub (list (substr line 2 (- (strlen line) 2))))
          )
          ( (or (eq "" line) (eq "#" (substr line 1 1))) )
          (t
            (setq i 0 m (strlen line))
            (while (and (<= (setq i (1+ i)) m) (/= "=" (substr line i 1))))

            (if (<= i m)
              (setq sub (cons (cons (substr line 1 (1- i)) (substr line (1+ i))) sub))
            )
          )
        )
      )
      (close file)
      (if sub (setq lst (cons (reverse sub) lst)))
    )
  )
  (reverse lst)
)

To parse the config file into an association list, which may be manipulated with much less code. Note that the code is quite verbose since the use of Visual LISP functions is not allowed.

The above function will read a file such as:

Code: [Select]
[SECTION1]
KEY1=VALUE1
KEY2=VALUE2

[SECTION2]
KEYA=VALUEA
KEYB=VALUEB

#this is a comment

[SECTION3]
KEYX=VALUEX
KEYY=VALUEY

#end

And return a list:

Code: [Select]
(
  ("SECTION1"
    ("KEY1" . "VALUE1")
    ("KEY2" . "VALUE2")
  )
  ("SECTION2"
    ("KEYA" . "VALUEA")
    ("KEYB" . "VALUEB")
  )
  ("SECTION3"
    ("KEYX" . "VALUEX")
    ("KEYY" . "VALUEY")
  )
)

Now the above list can be queried using LISP's assoc functions:

Code: [Select]
(defun XGetValue ( alist section key )
  (if (setq section (cdr (assoc section alist))) (cdr (assoc key section)))
)

Code: [Select]
(setq lst (XParseData "C:\\SomeFolder\\SomeFile.ini"))

(XGetValue lst "SECTION2" "KEYB")
==>  "VALUEB"

Writing is a different issue however, since the above list is devoid of comments.

Lee

Lee Mac

  • Seagull
  • Posts: 12905
  • London, England
Re: AutoLISP Config Class
« Reply #3 on: March 29, 2011, 08:45:53 AM »
Alternatively, you could include the empty lines and comments in the output list:

Code: [Select]
(defun XParseData ( file / line sub lst i m ) 
  (if (and (setq file (findfile file)) (setq file (open file "r")))
    (progn
      (while (setq line (read-line file))
        (cond
          ( (eq "[" (substr line 1 1))

            (if sub (setq lst (cons (reverse sub) lst)))
            (setq sub (list (substr line 2 (- (strlen line) 2))))
          )
          ( (or (eq "" line) (eq "#" (substr line 1 1)))

            (setq sub (cons line sub))
          )
          (t
            (setq i 0 m (strlen line))
            (while (and (<= (setq i (1+ i)) m) (/= "=" (substr line i 1))))

            (if (<= i m)
              (setq sub (cons (cons (substr line 1 (1- i)) (substr line (1+ i))) sub))
            )
          )
        )
      )
      (close file)
      (if sub (setq lst (cons (reverse sub) lst)))
    )
  )
  (reverse lst)
)

Code: [Select]
(
  ("SECTION1"
    ("KEY1" . "VALUE1")
    ("KEY2" . "VALUE2")
    ""
  )
  ("SECTION2"
    ("KEYA" . "VALUEA")
    ("KEYB" . "VALUEB")
    ""
    "#this is a comment"
    ""
  )
  ("SECTION3"
    ("KEYX" . "VALUEX")
    ("KEYY" . "VALUEY")
    ""
    "#end"
  )
)

This would make it easier to add sections to the file, alter the list and write the list back to the file, however the XGetValue function would need to be modified to account for 'empty sections' containing only empty lines and/or comments.

Lee


Xander

  • Guest
Re: AutoLISP Config Class
« Reply #4 on: March 29, 2011, 07:45:38 PM »
Thanks Lee!

The major benefit is I only need to read the config file once (unless it's been updated). I'm sure I can manipulate these to append and create.

Thanks once again.

Lee Mac

  • Seagull
  • Posts: 12905
  • London, England
Re: AutoLISP Config Class
« Reply #5 on: March 29, 2011, 07:47:50 PM »
You're welcome Xander, shout if you need a hand  :wink:

Lee Mac

  • Seagull
  • Posts: 12905
  • London, England
Re: AutoLISP Config Class
« Reply #6 on: March 30, 2011, 09:35:34 AM »
Hi Xander,

Having thought a little more about it, perhaps this would be a better approach:

XParseConfig

Code: [Select]
(defun XParseConfig ( file / line sub lst i m ) 
  (if (and (setq file (findfile file)) (setq file (open file "r")))
    (progn
      (while (setq line (read-line file))
        (cond
          ( (wcmatch line "`[*`]")

            (if sub (setq lst (cons (reverse sub) lst)))
            (setq sub (list (substr line 2 (- (strlen line) 2))))
          )
          ( (wcmatch line "*=*")

            (setq i 0)
            (while (/= "=" (substr line (setq i (1+ i)) 1)))

            (setq sub (cons (list (substr line 1 (1- i)) (substr line (1+ i))) sub))
          )
          ( (setq sub (cons (list line) sub)) )
        )
      )
      (close file)
      (if sub (setq lst (cons (reverse sub) lst)))
    )
  )
  (reverse lst)
)

XGetValue

Code: [Select]
(defun XGetValue ( alist section key )
  (if (setq section (cdr (assoc section alist))) (cadr (assoc key section)))
)

XPutValue

Code: [Select]
(defun XPutValue ( alist section key value / sectionl )
  (if (setq sectionl (cdr (assoc section alist)))
    (setq sectionl (subst (list key value) (assoc key sectionl) sectionl)
          alist    (subst (cons section sectionl) (assoc section alist) alist)
    )
  )
)

XWriteConfig

Code: [Select]
(defun XWriteConfig ( alist file )
  (if (setq file (open file "w"))
    (progn
      (foreach section alist
        (write-line (strcat "[" (car section) "]") file)
        (foreach key (cdr section)
          (write-line (if (cadr key) (strcat (car key) "=" (cadr key)) (car key)) file)
        )
      )
      (not (close file))
    )
  )
)

Examples of Usage:

Config File Content:
Code: [Select]
[SECTION1]
KEY1=VALUE1
KEY2=VALUE2

[SECTION2]
KEYA=VALUEA
KEYB=VALUEB

#this is a comment

[SECTION3]
KEYX=VALUEX
KEYY=VALUEY

#end

Resultant List from XParseConfig:
Code: [Select]
_$ (setq lst (XParseConfig "C:\\test.txt"))

(
  ("SECTION1"
    ("KEY1" "VALUE1")
    ("KEY2" "VALUE2")
    ("")
  )
  ("SECTION2"
    ("KEYA" "VALUEA")
    ("KEYB" "VALUEB")
    ("")
    ("#this is a comment")
    ("")
  )
  ("SECTION3"
    ("KEYX" "VALUEX")
    ("KEYY" "VALUEY")
    ("")
    ("#end")
  )
)

XGetValue Example:
Code: [Select]
_$ (XGetValue lst "SECTION2" "KEYB")
"VALUEB"

XPutValue Example:
Note that XPutValue will alter the value of the supplied key for all occurrences of that key, similarly, the section will be updated for all occurrences of that section.
Code: [Select]
_$ (setq lst (XPutValue lst "SECTION2" "KEYB" "NEWVALUE"))

(
  ("SECTION1"
    ("KEY1" "VALUE1")
    ("KEY2" "VALUE2")
    ("")
  )
  ("SECTION2"
    ("KEYA" "VALUEA")
    ("KEYB" [color=green]"NEWVALUE"[/color])
    ("")
    ("#this is a comment")
    ("")
  )
  ("SECTION3"
    ("KEYX" "VALUEX")
    ("KEYY" "VALUEY")
    ("")
    ("#end")
  )
)

XWriteConfig Example:
Note that XWriteConfig will create a new config file if the supplied filename does not exist.
Code: [Select]
_$ (XWriteConfig lst "C:\\test.txt")
T

Hopefully this is useful for you.

Kind Regards,

Lee

Xander

  • Guest
Re: AutoLISP Config Class
« Reply #7 on: April 06, 2011, 08:37:29 AM »
Wow, thanks Lee!
I went away for a week and spent most of my train and bus rides developing a similar model for this, however I was looking at putting the value in then forcing a config write before anything happened (like a crash or similar).

Thank-you greatly for this, it's an excellent starting (maybe finishing?) point!

Regards,
Xander

Lee Mac

  • Seagull
  • Posts: 12905
  • London, England
Re: AutoLISP Config Class
« Reply #8 on: April 06, 2011, 08:46:22 AM »
You're very welcome Xander - I enjoyed writing them!  :-)