help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Writing to buffer/file


From: Pascal J. Bourguignon
Subject: Re: Writing to buffer/file
Date: Wed, 08 Dec 2010 15:28:27 -0000
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (darwin)

Michael Powe <michael+gnus@trollope.org> writes:

> Hello,
>
> I am writing a function in elisp to do the following:
>
> open and read an .ini file
> search the file for header lines of format [someheader]
> put header value as key of hash
> continue to search for string value (e.g., "profilename=.*")
> add found string value to list which is set as value for the header key 
> for each header, a small number of items will be added to the list
> after reading through ini file and filling the hash:
> open another buffer and write the contents of the hash to the buffer
> write the buffer to file
>
> Here is my function:
>
> (defun extract-ini-settings ()
>   (interactive)
>   (let (myhash mybuff myfile header strmatch results output strvalues mylist)
>       (setq myfile "c:\\share\\reports_default.ini")
>       (setq myhash (make-hash-table :test 'equal))
>       (setq mybuff (find-file myfile))
>       (set-buffer mybuff)
>       (setq strvalues "")
>       (while
>               (re-search-forward
>                "\\[customtableprofile[0-9]+\\]"
>                nil t)
>         (when (match-string 0)
>               (setq header (match-string 0))
>               (puthash header '() myhash)
>               (while 
>                       (re-search-forward
>                        "profilename=.*" nil t)
>                 (when (match-string 0)
>                       (setq strmatch (match-string 0))
>                       (puthash header (cons strmatch (gethash header myhash)) 
> myhash)))))
>       (kill-buffer mybuff)
>       (message (number-to-string (hash-table-count myhash)))
>       (setq output "c:\\share\\reports_headers.txt")
>       (setq results (find-file output))
>       (set-buffer results)
>       (point-min)
>     ;; this works, i.e. writes the string to the `results' buffer
>     ;; and each time I run the function, a new copy of the string
>     ;; is appended to the buffer.
>       (insert "Start of file\n")
>       (point-max)
>       (hash-to-list (myhash mylist))
>       (insert mylist)
>       ))
>
> Here is the helper function hash-to-list:
>
> (defun hash-to-list (hashtable mylist)
>   "Return a list that represent the hashtable."
>     (maphash (lambda (kk vv) (setq mylist (cons (list kk vv) mylist))) 
> hashtable)
>     mylist)
>
> I am evidently grossly misunderstanding the mechanism for writing to
> file. 


Even before that, you're misunderstanding the basic syntaxis for Lisp.

To call a function in lisp, we write a LIST, whose first element is the
name of the function, and whose other elements are the arguments to
that function.

To write a list, we write an open parenthesis, the elements of the list
separated by spaces, and a close parenthesis.


sexp ::= atom | '(' {sexp} ')' .

function-call ::= '(' function-name {argument} ')'






    (hash-to-list (myhash mylist))

Since myhash is not the name of a function the subexpression

    (myhash mylist) 

is meaningless, and since hash-to-list is defined to take two arguments,
this function call is wrong since you're trying to pass only one
(erronous) argument.



Finally, as you have noted, arguments are passed by value, so when you
call:

    (hash-to-list myhash mylist)

hash-to-list has no way to modify mylist.  I assume this is why you're
correctly returning from hash-to-list the local variable mylist which
contains the result of hash-to-list.   In any case, you will have to
rebind this result to the mylist of extract-ini-settings:

   (setf mylist (hash-to-list myhash mylist))
   (insert mylist)

or even better, just insert it directly:

   (insert (prin1-to-string (hash-to-list myhash mylist)))

Notice that insert can insert only strings, so you have to convert your
list into a string first.


Since mylist is bound to nil, there's not really any point in passing it
to hash-to-list.  


So you could write:

(require 'cl)

(defun hash-to-list (hashtable)
  "Return a list that represent the hashtable."
  (let ((result '()))
     (maphash (lambda (kk vv) (push (list kk vv) result))
              hashtable)
     result))



(let ((h (make-hash-table)))
   (setf (gethash :one h) 1
         (gethash :two h) 2
         (gethash :six h) 6)
   (insert (prin1-to-string (hash-to-list h))))

inserts:  ((:six 6) (:two 2) (:one 1))





In general, it is better of you can write functionnal code, that is, if
you can avoid modifying the bindings of the variables (avoid using setq
of setf).    This means that when you use let, you SHOULD give a value
to be bound to each variable, and not change it thereafter.

Also, it would  be better if you parameterized  your functions, ie. avoid
putting constants, such as pathnames inside your functions.

It would be nice if you avoided to to define variables that you don't
use.  (But do define variables that you use!).




(defun extract-ini-settings (ini-file dictionary-file)
  (interactive)
  (let ((dictionary (make-hash-table :test (function equal))))
    
    (with-temp-buffer
      (insert-file-contents ini-file)
      (while (re-search-forward "\\[customtableprofile[0-9]+\\]" nil t)
        (when (match-string 0)
          (let ((section  (match-string 0))
                (profiles '())
                (start    (point)) ; save the beginning of the section
                (end   (if (re-search-forward "\\[customtableprofile[0-9]+\\]" 
nil t)
                           (point) ; don't search profilename beyond the next 
section!
                           (point-max))))
            (goto-char start)
            (while (re-search-forward "profilename=.*" end t)
              (when (match-string 0)
                (push (match-string 0) profiles)))
            (setf (gethash section dictionary) profiles)))))

    ;; Are you sure you want to accumulate dictionaries in the output
    ;; file?
    ;; Here, I'm just replacing the whole contents with a single list:
    
    (find-file dictionary-file)
    (erase-buffer)
    (insert ";; -*- mode:lisp; coding: utf-8 -*-\n")
    (insert (format ";; Data extracted from %s\n\n" ini-file))
    (insert (prin1-to-string (hash-to-list dictionary)))
    (save-buffer)
    (kill-buffer)))


(extract-ini-settings
 "c:\\share\\reports_default.ini"
 "c:\\share\\reports_headers.txt")

(extract-ini-settings "/tmp/a.ini" "/tmp/a.txt")
(insert-file-contents "/tmp/a.txt")

;; -*- mode:lisp; coding: utf-8 -*-
;; Data extracted from /tmp/a.ini

(("[customtableprofile1]" ("profilename=tutu\"" "profilename=tata")) 
("[customtableprofile0]" ("profilename=titi\"" "profilename=toto")))


> Any help in correcting my errors would be greatly appreciated.

Finally, as you can see with my test file, using regular expressions is
a big source of errors.  

Well, if your ini file don't contain such "pathological" cases, they
could work, but in general, it would be better to write a parser.


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


reply via email to

[Prev in Thread] Current Thread [Next in Thread]