All I needed was a simple way for Safari to refresh the current page at fixed intervals.
Luckily, it's scriptable...
on idle
-- the applescript only calls attention to itself when the idle period is up
tell application "Safari"
--confirms your browser is the front application
activate
end tell
tell application "System Events"
tell process "Safari"
keystroke "r" using {command down}
end tell
end tell
--sets the idle period in seconds. i.e. safari will refresh every 10 seconds
return 10
end idle
I decided that before really writing off the fa.haskell post at How would you replace a field in a CSV file? about replacing a field in a CSV file using Haskell, I'd do something roughly equivalent in Common Lisp which I'm just learning.
1) Splitting the string
I needed the equivalent of String.split... quick check of the
Hyperspec and my other references for string and sequence functions
turned up some simple primitives, but no "split". CLiki came to my
rescue with the community sanctioned "split-sequence" package.
First, install it!
CL-USER> (require 'asdf-install)
("ASDF-INSTALL")
CL-USER> (asdf-install:INSTALL "split-sequence")
Install where?
1) System-wide install:
System in /usr/local/lib/sbcl/site-systems/
Files in /usr/local/lib/sbcl/site/
2) Personal installation:
System in /Users/cbrown/.sbcl/systems/
Files in /Users/cbrown/.sbcl/site/
--> 2
Downloading 2601 bytes from http://ftp.linux.org.uk/pub/lisp/cclan/split-sequence.tar.gz ...
Installing /Users/cbrown/split-sequence.asdf-install-tmp in /Users/cbrown/.sbcl/site/,/Users/cbrown/.sbcl/systems/
Next, figure out how to use it!
CL-USER> (split-sequence:split-sequence #\Comma "bar,foo,baz")
("bar" "foo" "baz")
11
2) Doing the replacements via hashtable mapping
Now, I need a replace...Lisp's elt is a setf-able place, so:
CL-USER> (let ((boo (split-sequence:split-sequence #\Comma "bar,foo,baz")))
(setf (elt boo 1) "ganesh")
boo)
("bar" "ganesh" "baz")
On second thought, for now I'll do this the purely functional way and
return new sequences, performing the replacements as I push into the
new sequence.
Ok, next step is to create the map of things we'll be replacing. Lisp
has a hashtable of course!
From the SLIME auto-complete:
(make-hash-table &key (test 'eql) (size sb-impl::+min-hash-table-size+) (rehash-size 1.5) (rehash-threshold 1) weak-p)
The first argument is a keyword arg used to replace the default test
function, which is 'eql. If you plan on putting keys in your
hashtable better served by some other comparison, you have that
ability. Since we'll be using strings, we'll want #'equal as our
comparator. Don't worry much about the rest of the hoo-hah. Next are
the minimum size, the rehash size and threshold and I'm not sure what
weak-p is all about.
(gethash key hash-table default) does pretty much the same thing as
the example in the article. It returns the value for the key, or the
default if no mapping is found.
3) Reading the file... and the rest
Putting it together, we need to read each line from the file, split
into an array of strings by comma, replace strings with values from
the hash-table and write the lines back out. Oh, that means we need
to be able to 'join' the list of strings with commas. For this part,
I cheated. 'format' has wicked iteration capabilities and does the
complete job of joining the list back into a single string, with
comma-separated fields:
(format t "~{~a~^,~}" boo)
What I haven't mentioned so far is the file handling. How do we open
a file and iterate across its lines? Let's see 'with-open-file',
'read-line' and the rest, all put together:
(require 'split-sequence)
(defvar *ht* nil)
(setf *ht* (make-hash-table :test #'equal))
(setf (gethash "bob" *ht* ) "ganesh")
(setf (gethash "kennewick" *ht* ) "lake chelan")
(with-open-file (in (merge-pathnames #P"Desktop/crap.txt" *default-pathname-defaults*))
(do ((l (read-line in) (read-line in nil 'eof)))
((eq l 'eof))
(let ((boo (split-sequence:split-sequence #\Comma l))
(newlist '()))
(dolist (b boo)
(push (gethash b *ht* b) newlist))
(format t "~{~a~^,~}~%" (nreverse newlist)))))
Gee, that was easy. Note - I sat down for about 1 hour and figured
this out. It should have been faster, but I was often distracted by a
3 year old :) The code to walk the file is the only confusing part.
For reference, here's the same Ruby code:
ht = {"bob"=>"ganesh"}
File.open(File.join("/Users/cbrown","Desktop/crap.txt")) do |file|
file.each do |line|
output_line = []
line.split(",").each do |term|
output_line << (ht[term] || term)
end
puts output_line.join(",")
end
end
The Lisp version can be shortened in a few different ways, the least
of which would be a (with-lines...) macro reading the file much like
the XXX.each Ruby syntax. Not counting the initial assignment before
the file I/O starts, there are 8 working terms in the Lisp code
(with-open-file, do, let, dolist, push, gethash, format, reverse) and
8 in the Ruby code (File.open, File.join, file.each, line.split,
output_line <<, ht[term]||term, puts, output_line.join), making them
roughly equivalent with Ruby winning the clarity of syntax contest and
Lisp winning the flexibility, since there are a number of things that
could be done in the do construct simultaneously that aren't
illustrated here and format statement is hella powerful too.
Addendum: I liked this version a bit better for readability:
(with-open-file (in (merge-pathnames #P"Desktop/crap.txt" *default-pathname-defaults*))
(loop
(multiple-value-bind (line nl) (read-line in nil in)
(when (eq line in) (return))
(let ((boo (split-sequence:split-sequence #\Comma line))
(newlist '()))
(dolist (b boo)
(push (gethash b *ht* b) newlist))
(format t "~{~a~^,~}~%" (nreverse newlist))))))
Then I found this gem in The Common Lisp Directory at
and decided I'm a moron:
(defun file-lines (path)
"Sucks up an entire file from PATH into a list of freshly-allocated
strings, returning two values: the list of strings and the number of
lines read."
(with-open-file (s path)
(loop for line = (read-line s nil nil)
and line-count from 0
while line
collect line into lines
finally (return (values lines line-count)))))
Making the code above:
(dolist (line (file-lines (merge-pathnames #P"Desktop/crap.txt" *default-pathname-defaults*)))
(let ((boo (split-sequence:split-sequence #\Comma line))
(newlist '()))
(dolist (b boo)
(push (gethash b *ht* b) newlist))
(format t "~{~a~^,~}~%" (nreverse newlist))))