all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: pjb@informatimago.com (Pascal J. Bourguignon)
To: help-gnu-emacs@gnu.org
Subject: Re: Writing to buffer/file
Date: Tue, 14 Sep 2010 16:14:40 +0200	[thread overview]
Message-ID: <m239tcbeen.fsf@invitado-174.medicalis.es> (raw)
In-Reply-To: 87eicwfpjq.fsf@cecilia.trollope

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


  parent reply	other threads:[~2010-09-14 14:14 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <87eicwfpjq.fsf@cecilia.trollope>
2010-09-14 14:13 ` Writing to buffer/file Pascal J. Bourguignon
2010-09-14 14:14 ` Pascal J. Bourguignon [this message]
     [not found]   ` <0388da54-19f4-4aaa-90c6-8f6262c4164f@c21g2000vba.googlegroups.com>
2010-09-14 17:46     ` Pascal J. Bourguignon
2010-09-14 14:33 ` Pascal J. Bourguignon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=m239tcbeen.fsf@invitado-174.medicalis.es \
    --to=pjb@informatimago.com \
    --cc=help-gnu-emacs@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.