Get files in directory, print each by lines - lisp

I'm porting some of my python scripts to Common Lisp. I need to get list of files in some directory and print each file's content by lines.
This code shows me all filenames. But lines are printed only for last file. Why?
Also, what is the best way ti iterate by file lines?
Thanks.
(dolist (file (directory (make-pathname :name :wild
:type :wild
:defaults "path\\to\\files\\")))
(print file)
(with-open-file (stream file)
(do ((line (read-line stream) (read-line stream)))
(nil t)
(print line))))

I would propose to write a function which prints a file given a pathname and a stream.
You iterate with DO. That's okay, but I would use something like LOOP which allows slightly easier to read code.
Your DO is an endless loop. You might want to end the loop when the EOF is reached.
READ-LINE generates an error when it reads past the end of the file. Thus your code signals an error on the end of the first file. This error causes your code to only print the first file.
You need to call READ-LINE such a way that you test for EOF and end the iteration then. See the arguments to READ-LINE. Alternatively you can catch the EOF error, but the other solution is slightly easier.

This seems to work for me:
(dolist (file (directory (make-pathname :name :wild
:defaults "/tmp/lt/files/")))
(print file)
(with-open-file (stream file)
(do ((line (read-line stream nil) (read-line stream nil)))
((null line))
(print line))))

Related

How to extract titles as links from a list of Org files in a directory

I have a list of org files under a directory:
> org-file1.org
> org-file2.org
> org-file3.org
> ...
> org-fileN.org
I want to extract their titles (using #+title tag) as links as follows:
[[file:org-file1.org][title for org file 1]]
[[file:org-file2.org][title for org file 2]]
[[file:org-file3.org][title for org file 3]]
...
[[file:org-fileN.org][title for org file N]]
How can I extract these as a list using Emacs Lisp?
You could use the org-element-api for this, but in this case it is probably easier/faster to simply search by regexp using looking-at-p. To get all files in a directory, there is directory-files(-recursively). Then finally you could achieve it using the following function:
(defun extract-org-directory-titles-as-list (&optional dir)
(interactive "D")
(print
(delete nil
(let ((case-fold-search t))
(mapcar (lambda (f)
(when (string-match "org$" f)
(with-temp-buffer
(insert-file-contents-literally
(concat (file-name-as-directory dir) f))
(while (and (not (looking-at-p "#\\+TITLE:"))
(not (eobp)))
(forward-line))
(when (not (eobp))
(cons f (substring (thing-at-point 'line) 9 -1))))))
(directory-files dir))))))
(defun insert-directory-org-file-titles (&optional dir)
(interactive "D")
(let ((files-titles (extract-org-directory-titles-as-list dir)))
(dolist (ft files-titles)
(insert (concat "[[file:" (car ft)"][" (cdr ft) "]]\n")))))
If you prefer to just have it as a (a)list, like you asked, then just use extract-org-directory-titles-as-list.
Here's some code that can be used on a single file to get the title. It's part of an org mode file that can be used for testing. Save it as an Org mode file, visit it in Emacs and type C-c C-c on the code block. Change the title and evaluate the code block again.
#+OPTIONS: toc:nil
#+TITLE: This is a very nice title, don't you think?
#+AUTHOR: A.U. Thor
* foo
bar
* Code
#+begin_src elisp :results drawer
(save-excursion
(goto-char (point-min))
(let ((case-fold-search t))
(search-forward-regexp "^#\\+TITLE:")
(org-element-property :value (org-element-context (org-element-at-point)))))
#+end_src
#+RESULTS:
:results:
This is a very nice title, don't you think?
:end:
The code block goes to the beginning of the buffer, searches forward for the string #+TITLE: at the beginning of a line (the search is case-insensitive) and then uses some functions from the org-element library to parse the buffer at point, get the context and get the :value property out of it.
In order to make it into a complete solution, you have to:
make it more robust: check that this is a title keyword not just some random junk that happened to satisfy the match, although that's unlikely; but better safe than sorry.
wrap it in a loop that does a find-file on every file in the directory and gets the title for each file and returns a list of tuples: (filename title) that you can easily use to create your links.

Read from a file into a Emacs lisp list

I have the following file data.txt
A
B
C
D
I would like to read the contents of this file into a Lisp list, like
(defun read-list-from-file (fn)
(interactive)
(list "A" "B" "C" "D"))
(defun my-read ()
(interactive)
(let (( mylist (read-list-from-file "data.txt")))
(print mylist t)))
How can I modify read-list-from-file such that it returns the same list, but instead reads from the file given as input argument fn.. Each line in the file should be a separate item in the list..
This code:
(with-current-buffer
(find-file-noselect "~/data.txt")
(split-string
(save-restriction
(widen)
(buffer-substring-no-properties
(point-min)
(point-max)))
"\n" t))
UPD:
Here's a version with insert-file-contents:
(defun slurp (f)
(with-temp-buffer
(insert-file-contents f)
(buffer-substring-no-properties
(point-min)
(point-max))))
(split-string
(slurp "~/data.txt") "\n" t)
Much easier, than creating a temporary buffer, is to use f file manipulation library's f-read function that returns textual content of a file (default coding UTF-8).
f is a third-party that you need to install from MELPA, read Xah Lee's tutorial on how to use Emacs package management system. Then you could use either split-string or s string manipulation library's s-split (which is a simple wrapper around the former function):
(s-split "\n" (f-read "~/data.txt") t) ; ("A" "B" "C" "D")
Note: third parameter to s-split set to t omits empty strings. You could use s-lines, but as textual files on *nix systems usually contain a trailing newline, the returned list would be ("A" "B" "C" "D" ""). You can remove the last element with dash list manipulation library's butlast function, but this works in O(n), so probably stick to s-split, unless you want empty lines to be preserved in the list.

How can I idiomatically transform some text in place using elisp?

I've got some json that I'd like to process in emacs. I've found and used the elisp library to extract the desired content from the json, and I'd like to replace the json with the elisp equiivalent that I've extracted.
This is what I've written:
(defun extract-foo (start end)
"Extract the foo field from a json object in the region"
(interactive "r")
(let ((my_json (cdr (assoc 'FOO (json-read-from-string (buffer-substring-no-properties start end))))))
(delete-region start end)
(SOMETHING)
))
I'm stuck at the something. I can't seem to find a way to write the contents of my_json to the buffer at the mark. The only way I can think of is to save the text instead to a temporary buffer, and then (insert-buffer) it. This seems excessive to me though.
How can I do this idiomatically in elisp?
to write back JSON partial
(insert (format "%s" (json-encode my-json)))
to write back elisp:
(insert (format "%s" my-json))
Note that your use of underscore in variable naming is contrary to convention.

How to redirect standard output to a file in Elisp using EMACS

I use with-output-to-temp-buffer function, redirect the standard output to it, save it to a file, switch back to previous buffer, then kill the temp buffer.
(require 'find-lisp)
(with-output-to-temp-buffer "*my output*"
(mapc 'print (find-lisp-find-files "~/project/clisp" "\\.lisp$"))
(setq prev-buffer (buffer-name))
(switch-to-buffer "*my output*")
(write-region nil nil "test")
(switch-to-buffer prev-buffer)
(kill-buffer "*my output*")
)
But error below occur. I don't know why.
Debugger entered--Lisp error: (error "Selecting deleted buffer")
PS: Is there more elegant way to achieve this in elsip(redirect standard output to a file). Thanks
This error occurs because with-output-to-temp-buffer tries to display the buffer after evaluating its body, but at that point you have already deleted the buffer. I think with-temp-file is the macro you're looking for. Its docstring says:
(with-temp-file FILE &rest BODY)
Create a new buffer, evaluate BODY there, and write the buffer to FILE.
You can then bind standard-output to the new buffer, something like:
(with-temp-file "test.txt"
(let ((standard-output (current-buffer)))
(mapc 'print (find-lisp-find-files "~/project/clisp" "\\.lisp$"))))

Returning a List of Words from a File

My next project is writing a hangman game. I figured it would help me brush up on strings and file I/O.
Currently, i'm stuck on reading in a file of strings into a list. I'm trying to avoid global variables, so could someone point me in the right direction to make this (probably broken) code into a function that returns a list?
(defun read-word-list ()
"Returns a list of words read in from a file."
(let ((word-list (make-array 0
:adjustable t
:fill-pointer 0)))
(with-open-file (stream #p"wordlist.txt")
(loop for line = (read-line stream)
while line
(push line word-list)))
(select-target-word word-list)))))
You can read-in the words as Lisp symbols, with just a few lines of code:
(defun read-words (file-name)
(with-open-file (stream file-name)
(loop while (peek-char nil stream nil nil)
collect (read stream))))
Example input file - words.txt:
attack attempt attention attraction authority automatic awake
bright broken brother brown brush bucket building
comfort committee common company comparison competition
Reading the file:
> (read-words "words.txt")
=> (ATTACK ATTEMPT ATTENTION ATTRACTION AUTHORITY AUTOMATIC AWAKE BRIGHT BROKEN BROTHER BROWN BRUSH BUCKET BUILDING COMFORT COMMITTEE COMMON COMPANY COMPARISON COMPETITION)
The case could be preserved by enclosing the symbols in pipes (|) or declaring them as strings:
|attack| "attempt" ...
Reading without losing case:
> (read-words "words.txt")
=> (|attack| "attempt" ...)
If the words are one per line, you could do something like this:
(defun file-words (file)
(with-open-file (stream file)
(loop for word = (read-line stream nil)
while word collect word)))
You could then use it like this;
(file-words "/usr/share/dict/words")