用lisp開發(fā)博客客戶端
Author: |
Kevin Lynx |
Date: |
3.13.2011 |
最近一直在學習Lisp這門語言。回頭一看,基本上接近1個月了。剛開始接觸Lisp是因為看
了<Lisp本質(zhì)>,然后我發(fā)現(xiàn)有很多人宗教般地忠誠這門語言,于是就來了興趣。
當然并不是每次因為某篇寫得很geek技術文章就去學習某個新的技術點。一個月時間對我來
說還是很珍貴了。但是Lisp絕對是大部分程序員都值得一學的語言(就像Haskell一樣)。
我能給出的簡單理由包括:
- 大部分程序員只會命令式語言(C/C++/C Like etc),缺乏函數(shù)式語言解決編程問題的思
想(當然Lisp不是純函數(shù)式)
- Lisp是僅次于Fortran的古老語言,很多優(yōu)秀的語言設計思想在現(xiàn)代的一些語言里都找得
到
- 裝B黨必備
另一方面,結合我一個月以來的讀書和兩個練習工程的實踐經(jīng)歷,我覺得也有些理由值得你
不去學習Lisp:
- 你會Haskell或者其他函數(shù)式語言
- 我目前還是覺得Lisp學習曲線高(大概是因為我讀到的書都在應用語法層兜圈子,事實上
Lisp的語法之統(tǒng)一,全特么的是s-expression),你不愿意花費這些成本
- you are too old bo to be a B
關于這篇文檔
這篇博客我使用reStructuredText格式編寫,然后用docutls導出為html,再然后使用這回
用lisp開發(fā)的基于metaweblog API的博客客戶端,自動發(fā)布到CPPBLOG。
他們怎么說Lisp
我就摘錄些書上的觀點(歷史):
- 1958年,John McCarthy和他的學生搞出了Lisp,包括其第一個實現(xiàn),最初貌似也是以一
篇論文起頭
- Lisp可以讓你做其他語言里無法做的事情(<ANSI common Lisp>)
- 大部分編程語言只會告訴你不能怎樣做,這限制了你解決問題的思路,Lisp not (<ANSI
Common Lisp>)
- Lisp讓你以Lisp的思維思考問題,換到其他語言你會說:為什么X語言就不支持這個特性
呢(Once you've leanred Lisp, you'll even dream in Lisp) (<Land Of Lisp>)
- Lisp代碼更清晰地體現(xiàn)你的想法(<Practical Common Lisp>)
And my opinion
我可還沒到把Lisp捧上天的地步。如果Lisp如此之好,為什么用的人不多?<Land Of Lisp>
里作者恰好對這個問題做了回答(bla bla bla,懶得細讀)。
- Lisp也是一門雜和型風格的語言,函數(shù)式、命令式、面向?qū)ο螅约白畋蝗舜蹬醯暮昃幊?
--程序自己寫自己
- Lisp的語句全部以(xxx xxx)的形式出現(xiàn),被稱為s-expression,我看稱為括號表達式還
差不多
- Lisp每條語句都有返回值,沒基礎過函數(shù)式編程的同學,if語句也是有返回值的
- 函數(shù)式編程語言的一個重要特性就是閉包(closure),這個東西用來避免全局變量實在太
geek了
開始學習Lisp
Lisp不像有些語言,有個直接的機構來維護。感覺它更像C/C++一樣,只有個標準,然后有
若干編譯器(解釋器)實現(xiàn)。Lisp在幾十年的發(fā)展中,產(chǎn)生了很多種方言。方言也就是形變
神不變的語言變種,本文說的Lisp均指Lisp的方言Common Lisp。另一個比較有名的方言是
Scheme,關于各個方言的特點,<Land Of Lisp>里也給了一個圖片:
其中,最左邊那只wolf就是Common Lisp,右邊那只sheep就是Scheme。
要學習Lisp,首先就是選擇方言。然后最重要的就是選擇一個編譯器實現(xiàn)。世界上知名的有
十幾種實現(xiàn)(也許更多)。一些商業(yè)版本非常強大,甚至能編譯出很小的本地代碼執(zhí)行文件
,不過價格也不菲。當然也有很多開源免費的實現(xiàn),例如CLISP、SBCL。我選用的是SBCL。
SBCL交互式命令行不支持括號匹配,甚至沒有輸入歷史。要實現(xiàn)這兩個功能,可以裝一個
lisp工具:linedit。在lisp的世界中,要獲得一個lisp的庫實在不是件方便的事。尤其是
這些免費的編譯器實現(xiàn),并不像有些語言一樣,直接隨編譯器帶個幾十M的庫。
然后就有了quicklisp這個工具。該工具就像Ubuntu系統(tǒng)里的軟件管理器一樣,你可以在
lisp里直接獲取某個庫。quicklisp檢查該庫是否存在,不存在直接從它的服務器上下載人
然后自動安裝。
此外,在lisp的世界里,寫出來的程序不再是跨OS。OS的差異由編譯器實現(xiàn)來解決。但是,
寫lisp程序卻需要考慮跨編譯器實現(xiàn)(egg hurt)。這也是個無比傷神的事,比跨OS更傷
神。因為OS就那么幾個,但lisp的編譯器實現(xiàn),流行的也有好幾個。
lisp的世界里,工程組織也有特殊的一套,就像makefile一樣,這就是asdf。
博客客戶端如何實現(xiàn)
像我們這種基本沒接觸過Web開發(fā)的人,可能完全沒有思路去實現(xiàn)一個博客客戶端。事實上
實現(xiàn)起來非常簡單。
使用過其他博客客戶端(例如Windows Live writer)的人肯定知道m(xù)etaweblog API,在配
置客戶端的時候需要填入。例如CPPBLOG的這個地址就是
http://www.shnenglu.com/kevinlynx/services/metaweblog.aspx。這個頁面展示了一些API
說明。這些API就是博客客戶端和服務器進行操作通信的接口。意思是說,服務器端提供這
這些接口,我們的客戶端調(diào)用這些接口即可。例如:
blogger.deletePost,調(diào)用該接口即可刪除一篇博客文章
但是客戶端如何調(diào)用到這個接口呢?這需要通過一種新的技術(或者說標準),即 xml rpc
。rpc大家應該清楚,xml rpc其實說白了, 就是把接口調(diào)用的細則塞進 http
請求發(fā)給web服務器,服務器接收請求完成操作后再把結果以http回應的形式丟給客戶端,
即完成了一次接口調(diào)用 。
至于http請求回應的細則就不提了,無非就是一些特殊格式的數(shù)據(jù),通過tcp連接與服務器
交互這些數(shù)據(jù)。
所以,基本上,整個過程還是非常簡單。如何來將調(diào)用細節(jié)塞進http請求,則是以xml rpc
標準來做,其格式正好是xml格式。舉個例子吧:
<?xml version='1.0'?>
<methodCall>
<methodName>title_or_id</methodName>
<params>
</params>
</methodCall
當然這部分數(shù)據(jù)之前就是若干http請求的數(shù)據(jù)。服務器回應也是以xml格式組織:
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><string>Welcome to Zope.org</string></value>
</param>
</params>
</methodResponse>
我們的博客客戶端所要做的,就是把這些博客發(fā)布相關的操作封裝起來提供給使用者。底層
實現(xiàn)主要包括http請求、xml-rpc的組織等。何況,這兩部分在各個語言里都有大量的庫存
在,lisp自然也有。
我這里直接選取了lisp的一個xml-rpc庫:s-xml-rpc,基本上百來行代碼就可以把各個功
能跑一遍。例如以下lisp代碼就實現(xiàn)了通過s-xml-rpc刪除CPPBLOG的一篇文章:
(defun delete-post (postid)
(rpc-call
"blogger.deletePost"
postid
"kevinlynx"
"password"
t))
發(fā)布博客也很簡單,根據(jù)metaweblog API接口的說明,發(fā)布博客時需要填充一個結構體。但
主要涉及到的數(shù)據(jù)僅包括:文章內(nèi)容、文章標題、文章分類(可選):
(defun new-post (title context &optional (cates))
(rpc-call
"metaWeblog.newPost"
""
"kevinlynx"
"password"
(new-post-struct title context cates)
t))
值得注意的是,如果文章中有貼圖,則需要事先將圖片文件上傳到服務器。CPPBLOG的
metaweblog API里恰有API提供:
(defun new-media-object (filename)
(rpc-call
"metaWeblog.newMediaObject"
""
"kevinlynx"
"password"
(new-media-object-struct filename)))
該函數(shù)讀入圖片文件,然后調(diào)用metaWeblog.newMediaObject接口,即可完成上傳。上傳成
功后,服務器會返回該圖片的URL。然后在我們的文章中就可以使用該圖片了。
完整實現(xiàn)方案
僅僅將metaweblog的一些接口做封裝,對于一個可以使用的博客客戶端來說還遠遠不夠。大
部分同類工具都有一個友好的GUI編輯界面。我并不打算弄一個編輯界面出來,吃力不討好
的事情。
我的打算是先用其他工具對文章做排版處理,最后導出為html格式。因為CPPBLOG支持直接
發(fā)布一個html文件。然后在用這個lisp工具將整個文件作為博客文章內(nèi)容發(fā)布。
恰好公司最近打算用reStructureText(rst)格式來編輯文檔,作為熟悉手段,我決定拿這個
來練手。rst格式非常簡單,同wiki命令很相似。在vim里編輯該文件非常合適,因為默認支
持。見圖:
由圖即可看出,rst是一種半所見即所得的格式。即:它遵循你在編輯器里的排版,同時也
通過一些tag(例如image)來控制更豐富的輸出。
rst有很多前端工具,可以將rst文件輸出,例如rst2html.py就可以輸出為html。好吧,最
最終我們得到了html格式的博客文章。
但是如果文章中出現(xiàn)了圖片,而圖片基本上在本地,轉(zhuǎn)成html后也是相對路徑。我需要我的
lisp writer(cl-writer)能自動掃描文章,發(fā)現(xiàn)有圖片的地方,就自動將圖片上傳。最惡心
的是上傳后還得替換圖片引用路徑。這個工作可以在rst格式上做,也可以在結果格式html
上做。通過xml解析庫解析html比直接解析rst格式更簡單,并且在擴展性上更好。
最終這個html中圖片路徑替換工作只消耗了不到100行l(wèi)isp代碼。這在很大程度上也依賴于
s-xml庫的接口設計。
最終封裝好的發(fā)布接口如下,從這里也可以看出,函數(shù)式語言鍛煉我們寫出功能單一代碼度
短小的接口:
(defun writer-post-new (post-file &key (u (get-default-user))(cates))
(read-post-file u post-file context title
(new-post u title context cates)))
END
別指望我發(fā)布的代碼能夠讓你一鍵在你的博客上留下"this is a test",你甚至別指望它能
能夠工作。但如果你本來就是一個資深的lisper,或者雖然不是lisper但卻執(zhí)意想看看結果
。這里我就簡要說說如何讓這些代碼歡樂起來:
OS Ubuntu10.04,下載安裝SBCL,不會有問題;
下載安裝quicklisp,官方文檔hand by hand,簡單不會有問題;
SBCL交互環(huán)境中使用quicklisp安裝s-xml-rpc:
(ql:quickload "s-xml-rpc")
裝載我的代碼:
(asdf:load-system :cl-writer)
在home下添加配置文件.cl-writer.lisp,配置你博客信息,例如:
(in-package cl-writer)
(setf *default-user* (make-cppblog-user "賬戶名" "密碼"))
如果你的博客不在CPPBLOG,雖然也許也是metaweblog,但我不能保證成功,配置文件則
要復雜點:
(setf *default-user* (make-user-info :name "帳戶名"
:password "密碼" :host "www.shnenglu.com"
:url "/kevinlynx/services/metaweblog.aspx"))
SBCL交互環(huán)境下測試:
(in-package cl-writer)
(new-post (get-default-user) "this is a test" "title")
下載代碼
最后,終于敲完這篇文章,我需要通過以下步驟來發(fā)表它:
in shell:
rst2html.py lisp_xml_rpc.rst lisp_xml_rpc.html
in SBCL:
(writer-post-new "lisp_xml_rpc.html")
;;EOF;;