- 东方地灵殿(東方地霊殿 〜 Subterranean Animism.)是日本同人社团上海爱莉丝幻乐团制作的一款弹幕系射击游戏
- wine 的版本为 1.1.5
- 无法启动 th11.exe,需要下载,或者从 Windows 下面复制一个 d3dx9_36.dll 文件,
$ cp d3dx9_36.dll ~/.wine/drive_c/windows/system32/ - 运行地灵殿的窗口模式的时候,分辨率似乎被限制在了640x480。没有找到解决办法
- 如果使用虚拟桌面(Virtual Desktop) + 地灵殿的全屏模式,感觉会有点卡,帧速降到50fps左右
- 如果使用虚拟桌面 + 窗口模式,游戏界面黑屏有声音
- 如果取消虚拟桌面 + 窗口模式,感觉上速度和 Windows 下面差不多,不过限速 60fps,实际差多少也不知道就对了
- 运行正确的话,wine 应该只会输出下面这几行消息,
$ wine th11.exe fixme:win:EnumDisplayDevicesW ((null),0,0x32f8d4,0x00000000), stub! fixme:d3d:WineD3D_ChoosePixelFormat Add OpenGL context recreation support to SetDepthStencilSurface fixme:d3d:WineD3D_ChoosePixelFormat Add OpenGL context recreation support to SetDepthStencilSurface fixme:win:WINNLSEnableIME hUnknown1 (nil) bUnknown2 1: stub! - 有一篇更加详细的说明,東方地霊殿に関するメモ(日语)
2008年10月14日
悠言悠闲:linux 下玩/wine 東方地霊殿
2008年9月20日
有备无患:豆瓣离线 - 利用 Google Gears 实现离线浏览豆瓣的 GreaseMonkey 脚本
豆瓣离线使用 Google Gears API 来实现离线浏览的功能。Google Gears 是一款 Google 开发的软件。Gears 通过 SQLite 数据库让客户端能够把网页暂存起来,并通过内部服务器(Local server)把数据库中暂存的网页重现,从而让用户实现离线上网的功能。Google Docs 和 Google Reader 都支持用 Gears 将本地暂存的资料与网络做同步。
豆瓣离线现在版本为 0.1,还有许多的不完善之处,欢迎大家的建议、意见和 Bug 报告:-)
功能
- 保存豆瓣上的某个页面,以及相关的图片、CSS 和 Javascript 脚本
- 按分类浏览已缓存的页面,目前的分类有:条目,小组和用户
使用方法
- 如果希望保存当前页面,我们先找到导航栏“退出”附近的“离线”。点击“离线”,待离线状态栏展开完毕后,点击“离线”下面的“收藏此页面”。注意,点击之后不要马上关闭窗口,因为相关的文件可能还在下载中。另外我们也可以打开 FireBug 的终端(Console)来看看究竟那些文件被下载下来了。
- 如果希望浏览已缓存的页面,
点击 Firefox -> File(文件) -> Work Offline(离线工作)来强制 Firefox 进入离线工作的状态。点击“离线” -> “离线浏览”,然后在缓存页面目录,我们可以点击其中的链接访问已被缓存的页面。 - 如果希望恢复在线浏览,在同样的地方点击“在线浏览”。
- 第一次使用豆瓣离线脚本时,Gears 会弹出两个安全警告对话框,都选择 Allow(允许)就可以了。
安装需求
- Firefox (>=3.0.0): http://www.mozilla.com/en-US/firefox/
-
GreaseMonkey (>=0.8):https://addons.mozilla.org/en-US/firefox/addon/748
- Google Gears (>=0.4):http://gears.google.com/
下载
大家可以从我的 GitHub Repo 中检出最新的脚本试用
git clone git://github.com/wuyuntao/douban-offline.git
或者从 UserScripts 网站上下载

2008-09-23 UPDATE 0.2:
豆瓣离线脚本更新为 0.2,有下面这些改动:- 可以手动切换上线/离线浏览
- 可以更新缓存页
- 新增两个分类,日记和相册
- 选中分类有高亮
2008-10-15 下载链接更新:
祸不单行。github 前几天数据库出了问题,到现在还没有恢复; userscripts.org 貌似被 GFW 给封了;今天 GAE 提供的四个 IP 中的又一个阵亡了。。。不管怎样,上面两个下载的链接都不管用了,现在提供一个本地的下载链接。
2008-10-22 UPDATE 0.4:
豆瓣离线脚本更新为 0.4,有下面这些改动:- 增加了翻页的功能
- 增加了评论和豆邮两个标签
- 增加了一条 Douban Helper 的控制台命令:write(wyt:Douban Helper 请安装最新版本)
- 改正了相册日记等页面离线按钮位置不正确的bug
2008年9月14日
有备无患:Google Reader 风的豆瓣广播 widget 更新
使用方法和以前的说明一样,只需要将下面这段代码插入到你的 blog 模板中,并替换其中的参数。
<script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?username=wyt&maxresults=16&style=blue"></script>
参数列表如下
- username: 用户名,或者用户 id。(必填)
- maxresults: 输出广播条目的数量。最大值为50,默认为10。
- style: widget 的配色样式,默认为绿色。其他样式包括 black(黑)、blue(蓝)、gray(灰)、green(绿)、khaki(卡其色)、pink(粉红)、slate(蓝灰) 和 none(无)。其中,none 为不带任何样式的原始 HTML,适合希望自定义广播风格的 bloggers。
- width: 手动设定 widget 的宽度,输入应为宽度的像素值,如 width=300。默认为自适应宽度(推荐)。
- hidetitle: 如果设 hidetitle=true,将隐藏顶部标题。默认为 false。
- hidefooter: 如果设 hidefooter=true,将隐藏底部豆瓣主页的链接。默认为 false。
隐藏标题和主页的效果如下
2008年9月12日
有备无患:升级到 Git 1.6.01 之后无法启动 git-daemon 的解决办法
自己也可动手,将启动脚本 /etc/init.d/git-daemon 中的 start 函数修改如下。
start() {
ebegin "Starting git-daemon"
start-stop-daemon --start --quiet --background \
--exec /usr/libexec/git-core/git-daemon -- ${GITDAEMON_OPTS}
eend $?
}
另外,我用来架设 Git 版本库的 gitosis 也和 git 1.6 有一点兼容问题,原因是 /usr/bin/git-shell 也搬到了 /usr/libexec/git-core 目录。好在已经有 patch 推出,只需要修改 /usr/lib/python2.5/site-packages/gitosis/serve.py,将其中调用 git-shell 的指令,替换成调用 git shell 即可。
--- a/gitosis/serve.py
+++ b/gitosis/serve.py
@@ -201,6 +201,6 @@ class Main(app.App):
sys.exit(1)
main_log.debug('Serving %s', newcmd)
- os.execvp('git-shell', ['git-shell', '-c', newcmd])
+ os.execvp('git', ['git', 'shell', '-c', newcmd])
main_log.error('Cannot execute git-shell.')
sys.exit(1)
2008年9月10日
南言北哲:Battier or Artest,谁将首发?
Artest。联盟最好的人盯人防守者之一,上赛季场均20.5分的进攻水准,以及38%的三分球命中率。加上曾在主教练 Rick Adelman 手底下打过一年球,对火箭现在这套动态进攻的体系了然在胸。如果在训练营和季前赛中一切顺利,他极有可能会取代 Shane Battier 成为火箭的正印前锋。
有同学认为,Artest 应妨 Luis Scola 之例,先从替补打起,走循序渐进的路线。可是你要知道,Scola 虽然在 FIBA 摸爬滚打了多年,但是他也需要时间来适应 NBA,另一方面主教练也需要时间来了解他的技术特点,所以 Scola 必须从替补打起。Artest 却是已经在联盟混迹了整九年,别说 Adelman,就是联盟其余的29位主教练也同样对他知根知底。
另外,虽然 Artest 曾经表过态,说愿意从替补打起。可是,以史为鉴,Artest 不是甘居替补之人——05年他炮轰 Jermaine O'Neal 出手权多和进攻效率低,被交易到 Sacramento 之后,又与 Mike Bibby 争当“国王”。(wyt: 最终都以阿泰“抢班夺权”失败而告终。)
2,如果 Battier 做替补,能不能胜任第六人的角色?
像 Battier 这样的防守型球员也一样可以胜任第六人,这甚至还有可能成为一股潮流。
联盟的大多数第六人都是长于进攻的球员,比如被交易回国王的 Bobby Jackson,湖人签下肥约的 Sasha Vujacic,小牛老将 Jerry Stackhouse,还有掘金的 J.R. Smith 等,他们的主要作用是在主力下场的时候带领其他替补球员,提供持续的进攻力,并为主力争取更多的休息时间。(wyt:我不喜欢把 Manu Ginobili 和 Jason Terry 归类到第六人。如果非要说,我更愿意称他们为“伪第六人”,或者,“联盟第六人奖项欺诈师”)
但是对于拥有所谓的“三巨头”的球队,这个角色常常是由“三巨头”的其中之一所担任,比如凯尔特人的 Ray Allen 和马刺的 Manu Ginobili。因而这些球队对第六人在进攻端的要求也有所不同。以 Boston 为例,上赛季第六人就是场均只有7.4分进帐的防守悍将 James Posey。总决赛中出色的表现,也让黄蜂弃用原来的第六人 Jannero Pargo,花重金将 Posey 挖来作为下赛季他们的第六人。另外,湖人下赛季也有可能让 Odom 出任第六人。
Battier 之于火箭,就像 Posey 之于 Boston 和 New Olean。而且 Battier 有更出色的协防能力和不错的背身技巧,可以想见新赛季的第四节,我们经常会看到 Battier 和 Artest 一起打完最后一分钟。
3,如果 Artest 做先发,出手次数会不会不够?
这很可能是杞人忧天。
上赛季,有三支球队的核心球员被称作“三巨头”,分别是凯尔特人的 Garnett、Pierce 和 Allen,马刺的 Duncan、Parker 和 Ginobili,以及湖人的 Kobe、Odom 和 Gasol。我们首先来看看他们的出手分配情况,并和火箭作一下对比。
表格中的 FGA 为出手次数 (Field Goal Attempts),FTA 为罚球次数 (Free Throw Attempts),TP 为球队节奏 (Team Pace),TSA 为每100次球权的真实出手次数 (True Shooting Attempts),所使用的统计公式为 TSA = (FGA + 0.44 * FTA) / TP * 100。要说明的,湖人的数据是“打劫” Gasol 以后的全明星之后的数据,而其他球队是整个赛季的数据。

从图表中,我们可以看出光是姚明,T-Mac 和 Battier 三个人的出手次数,就已经和其他几个三巨头基本持平。所以即使 Artest 取代 Battier 成为先发,只要姚明和 T-Mac 愿意牺牲自己的球权,火箭的出手次数对于 Artest 来说是绰绰有余的。
另外,由于伤病的关系,姚明和 T-Mac 都有相当长一段时间单独带队,所以也有同学质疑,这段时间他们的出手数是不是突增的,会不会影响到最后的统计结果。我也捎带统计了一下他们同时带队的10月、11月和2月份,姚明的出手数是20.0次,而 T-Mac 的是 24.4次。相比整个赛季并没有太大的变化,这主要是因为无论是姚明还是 T-Mac 带队,他们都是以火箭这个团队来进攻,谁也没有像 Kobe 在 Bynum 和 Gasol 缺席的时候那样贸然增加自己的出手。
2008年9月5日
有备无患:用 Gaupol 生成文本字幕
下面的命令示范了利用 Gaupol 生成一个 SubRip 格式的字幕文件,以作备忘。
$ python
>>> from gaupol.files.subrip import SubRip
>>> from gaupol import NEWLINE
>>> srt = SubRip('/home/wyt/demo.srt', encoding='utf-8', newline=NEWLINE.UNIX)
>>> srt.write(starts=[u'00:00:5.000', u'00:00:15.000', u'00:00:25.000'], \
ends=[u'00:00:10.000', u'00:00:20.000', u'00:00:30.000'], \
texts=[u'字幕测试一', u'字幕测试二', u'字幕测试三'])
生成的 .srt 字幕文件如下
1
00:00:5,000 --> 00:00:10,000
字幕测试一
2
00:00:15,000 --> 00:00:20,000
字幕测试二
3
00:00:25,000 --> 00:00:30,000
字幕测试三
2008年8月10日
有备无患:truncatehanzi - 按字数截取中文的 Django template filter
使用方法和 truncatewords 一样,
{{ value|truncatehanzi:6 }}
如果 value 为“截取English、Γρεεκ等字母语言和CJK汉字。”,输出“截取English、Γρεεκ等字...”。代码分为两个部分,截取汉字的脚本 truncate_hanzi.py 和生成过滤器的脚本 filters.py。
truncate_hanzi.py
# -*- coding: UTF-8 -*-
import re
from django.utils.encoding import force_unicode
from django.utils.functional import allow_lazy
def truncate_hanzi(s, num):
s = force_unicode(s)
length = int(num)
if length <= 0:
return u'...'
# Set up regex for alphanumeric characters
# \u00c0-\u02af: Latin
# \u0370-\u1fff: Greek and alphabet characters in other language
re_alnum = re.compile(ur'[a-zA-Z0-9_\-\u00c0-\u02af\u0370-\u1fff]', re.U)
# Set up regex for hanzi
# \u3040-\ufaff: CJK characters
re_hanzi = re.compile(ur'[\u3040-\ufaff]', re.U)
hanzi = u''
hanzi_len = 0
word_temp = u''
for char in s:
# Check for alphabet characters
if re_alnum.match(char):
word_temp += char
continue
if word_temp:
hanzi += word_temp
hanzi_len += 1
word_temp = ''
# Check for length
if hanzi_len >= length:
if not hanzi.endswith('...'):
hanzi += '...'
break
# Check for hanzi
if re_hanzi.match(char):
hanzi_len += 1
hanzi += char
hanzi += word_temp
return hanzi
truncate_hanzi = allow_lazy(truncate_hanzi, unicode)
def demo():
print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 6)
print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 11)
print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 20)
if __name__ == '__main__':
demo()
filters.py
# -*- coding: UTF-8 -*-
from django.template import Library
from django.template.defaultfilters import stringfilter
register = Library()
@stringfilter
def truncatehanzi(value, arg):
"""
Truncates a string after a certain number of words including
alphanumeric and CJK characters.
Argument: Number of words to truncate after.
"""
from truncate_hanzi import truncate_hanzi
try:
length = int(arg)
except ValueError: # Invalid literal for int().
return value # Fail silently.
return truncate_hanzi(value, length)
truncatehanzi.is_safe = True
register.filter('truncatehanzi', truncatehanzi)
2008年8月6日
有备无患:一个 jQuery 的时间选择插件
这是我的第一个 jQuery 插件,欢迎大家使用,我也会继续完善这个插件。如果有任何的 Bug,建议和意见,欢迎在文章后留言,或者 Email 给我,或者在插件的主页写下你的评论。:-)
使用方法
$('#time-picker').timepicker();
截图
演示
演示网址下载
你可以选择下载压缩档。
或者用 Git 从架设在 GitHub 的软件仓库中检出源代码使用。
$ git clone git://github.com/wuyuntao/timepicker.git
2008年8月4日
有备无患:Google Reader 风的豆瓣广播 widget
言归正传,我写了一个类似 Google Reader 的分享阅读剪辑(Shared Item Clips)风格的豆瓣广播 widget,可以放在 blog 的侧边栏。使用的方法很简单,只需要将下面这段代码插入到你的 blog 模板中。(wyt:如果不知道怎么操作的话,可以参考豆瓣秀指南。)
<script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?username=wyt&maxresults=16&style=blue"></script>
注意上面代码中粗体的部分,首先我们要将``wyt``换成自己的用户名;``maxresults``为 widget 显示的广播条数(最大值为50,默认为10,可以省略);``style``表示 widget 的配色,和 Google Reader 分享阅读的配色基本一致,包括:black、blue、gray、green、khaki、pink、slate 和 none。默认为 green,如果输入的 style 不在上述配色中,则返回 none。none 为不带任何 style 的原始 HTML,适合希望自定义广播风格的 blogger。
最终效果如下图。想看看实物 demo 的话,可以点击这里。要说明的是,看得到漂亮的圆角效果的只有 Firefox 用户(wyt:这也是 Google Reader 的风格之一,:p),IE、Opera 和 Safari 用户对不住了。如果有任何的建议或意见,欢迎留言 :-)
2008年7月18日
有备无患:在 Django 上使用 reCAPTCHA 生成验证码
除此之外,reCAPTCHA 还有一项非常公益的设计。虽然 OCR (全称 Optical Character Recognitio,中译光学字元识别)早已被投入使用,但是对那些印刷模糊或分辨度不足的实体书籍而言,OCR 的识别率难说完美。而 reCAPTCHA 将这些上无法由电脑自动识别的单词扫描下来,以图片的形式存入数据库,并在网络上以验证码(CAPTCHA)的形式交由人类来破译,而破译的结果将被用于帮助世界上的实体书加快数字化的进程。
你可能会问,既然电脑无法识别这些单词,那么 reCAPTCHA 系统又怎么知道用户填写的是正确的验证码呢?reCAPTCHA 的网站上有详细的解释。
一个不能被 OCR 正确识别的新单词,总是会和另一个已知答案的单词一起提交给用户。接下来用户需要同时识别这两个单词。如果用户正确识别出其中已知答案的那个单词,系统即假设用户所识别出的未知答案的新单词也可能是正确的。如果其他的用户也识别出相同的答案,系统则逐步提高这种可能性。最终当这种可能性超过某个阈值时,系统就可以将其认作已知答案的单词了。
言归正传,reCAPTCHA 有丰富的 API,包括对 PHP、JAVA、Ruby,自然还有 Python 的支持。如果要在 Django 上使用 reCAPTCHA,我们可以从 PyPI 下载 reCAPTCHA 的 Python 客户端,或者直接用 easy_install 安装。
# easy_install recaptcha-client
不知道为什么,虽然安装成功了,但是我不能导入 recaptcha 模块(wyt:import captcha 或 import recaptcha 都没有用,哪位如果顺利导入过的话,能不能分享一下你的经验呢?先谢了),所以我是把 egg 中的脚本直接复制到我的 Django 项目目录下了。
接下来,我们需要在 reCAPTCHA 注册并申请一对公钥/私钥,并将其保存在 settings.py 中。
settings.py
1 # reCAPTCHA keys
2 RECAPTCHA_PUBLIC_KEY = "6LdqgAIAAAUSHDmo4IIBmsjUsduAUMUoBDZc3J_T"
3 RECAPTCHA_PRIVATE_KEY = "6LdqgAIAAbjsJKLbj2KOjPO6D4isfJ_AzLSO_256"
然后,我们要为注册表单添加验证码了。在 Django Snippets 上,oggy 已经将 reCAPTCHA 抽象成一个 RecaptchaForm 类,所以我们只要让注册表单(Registration Form)继承这个类,就可以为注册表单添加验证码的功能了。由于 Django newform 默认是根据 field 的定义顺序来生成表单,所以在继承 RecaptchaForm 的时候,也应该把它放在最后继承。
recaptcha/forms.py
01 from django import newforms as forms
02 from django.newforms import ValidationError
03 from django.conf import settings
04 from recaptcha import captcha
05
06
07 class RecaptchaWidget(forms.Widget):
08 """ A Widget which "renders" the output of captcha.displayhtml """
09 def render(self, *args, **kwargs):
10 return captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY)
11
12 class DummyWidget(forms.Widget):
13 """
14 A dummy Widget class for a placeholder input field which will
15 be created by captcha.displayhtml
16
17 """
18 # make sure that labels are not displayed either
19 is_hidden=True
20 def render(self, *args, **kwargs):
21 return ''
22
23 class RecaptchaForm(forms.Form):
24 """
25 A form class which uses reCAPTCHA for user validation.
26
27 If the captcha is not guessed correctly, a ValidationError is raised
28 for the appropriate field
29 """
30 recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
31 recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
32
33 def __init__(self, request, *args, **kwargs):
34 super(RecaptchaForm, self).__init__(*args, **kwargs)
35 self._request = request
36
37 def clean_recaptcha_response_field(self):
38 if 'recaptcha_challenge_field' in self.cleaned_data:
39 self.validate_captcha()
40 return self.cleaned_data['recaptcha_response_field']
41
42 def clean_recaptcha_challenge_field(self):
43 if 'recaptcha_response_field' in self.cleaned_data:
44 self.validate_captcha()
45 return self.cleaned_data['recaptcha_challenge_field']
46
47 def validate_captcha(self):
48 rcf = self.cleaned_data['recaptcha_challenge_field']
49 rrf = self.cleaned_data['recaptcha_response_field']
50 ip_address = self._request.META['REMOTE_ADDR']
51 check = captcha.submit(rcf, rrf, settings.RECAPTCHA_PRIVATE_KEY, ip_address)
52 if not check.is_valid:
53 raise ValidationError('You have not entered the correct words')
user/forms.py
01 from myproject.recaptcha.forms import RecaptchaForm
02
03 class RegistrationForm(forms.Form):
04 """
05 Form for registering a new user account.
06
07 """
08
09 class RegistrationFormWithRecaptcha(RegistrationForm, RecaptchaForm):
10 """
11 Subclass of ``RegistrationForm`` and ``RecaptchaForm`` which can tell whether its user
12 is a human or a computer.
13
14 """
2008年7月4日
有备无患:浏览器端缓存(基于 jQuery)
最近在翻《Ajax 设计模式》,所以想把其中的一部分模式实现出来练手 jQuery,今天写了一个简单的引入 LRU 算法的浏览器端缓存(Browser Side Cache)。
浏览器端缓存用来保留服务器返回的查询结果。这种缓存是一个 Javascript 里类似映射的对象,存储成对的查询结果;查询是缓存的键(Key),服务器返回的结果是缓存的值(Value)。因此,每当浏览器需要查询服务器时,先检查缓存。如果该查询是缓存中的一个键,则与键对应的值将被当作结果,而不必再向服务器查询。
LRU (Least Recently Used)是将存储在缓存中的自上一次获取之后,最长时间未被使用的项目(Item)丢弃的一种算法。可以用两个数组(Array)来实现,其一是用来存储查询结果的键值对(Key-Value Pairs),其二是一个先进先出(FIFO)的队列,每一个新项目被塞入队列的尾部,并随着后续项目的跟进,逐渐逼近队列的头部。当队列全满时,每次向尾部塞入一个新项目,就要从头部弹出一个旧项目。而每当一个项目被缓存查询时,它会被送回到队列的尾部,这样可以确保最长时间未被使用的项目总是在开头处。
另外,还有一种常用的缓存算法,LFU (Least Frequently Used),它将自上一次获取后最少被使用的项目丢弃。
顺便一说,下面贴出的代码是由代码发芽网生产的纯 HTML。代码发芽网是一个“无需插件支持 Blog 代码高亮,支持近百种编程语言,多种配色主题支持,代码版本管理”的代码片段管理网站。因为不是由 Javascript 脚本来高亮处理,所以在 feed 里也一样可以看到效果,这一点很赞。不过,由于用处不大的 id 和 class 也包含在生成的 HTML 里,所以体积偏大。比如说下面这段代码,在删除了多余的 id 和 class,套上代码专用的 pre 和 code 标签,并将 还原成空格之后,体积可以缩小一半仅17KB(原来35KB),更适合 blog 来贴代码。
cache.js
01 function Cache(size) {
02 /* A browser side LRU cache
03 * Author: Wu Yuntao <http://luliban.com/blog/>
04 * License: GPLv3
05 *
06 * Usage:
07 * var cache = new Cache(10); // create a new cache object
08 * cache.put('w', 'wiki'); // put an item into cache
09 * cache.get('w'); // get the value of item with key
10 * cache.remove('w'); // remove an item with specified key
11 * cache.initialize() // re-initialize the cache
12 * cache.size(10) // resize the cache
13 */
14 this.initialize(size);
15 }
16
17 Cache.prototype = {
18 initialize: function(size) {
19 /* Initialize cache.
20 * ``size`` is the number of maxmium items this cache should hold.
21 * Default is maxium integer.
22 */
23 this._keys = new Array();
24 this._items = new Array();
25 this._size = size || this._size || Number.MAX_VALUE;
26 },
27
28 is_empty: function() {
29 /* Check if cache is empty
30 */
31 return (this._keys.length == 0 && this._items.length == 0);
32 },
33
34 size: function(size) {
35 /* Resize cache if ``size`` is specified, or return accual size of cache.
36 */
37 if (typeof size == 'undefined') return this._keys.length;
38 return this._size = size;
39 },
40
41 put: function(key, value) {
42 /* Put a new item into cache, if the size of cache reaches limit,
43 * cache will remove the least recently used (LRU) automatically.
44 * ``key`` of an item should be a string.
45 * ``value`` of an item could be anything, string, array or object.
46 * If ``value`` is not defined, returns null.
47 */
48 if (typeof value != 'undefined') {
49 this._keys.push(key);
50 this._items[key] = value;
51
52 if (this._keys.length > this._size) {
53 this.remove_least();
54 }
55 }
56 return value;
57 },
58
59 get: function(key) {
60 /* Retrieve an item by its key and move it to the tail of cache.
61 * If the item of ``key`` does not exist, returns null.
62 */
63 if (typeof this._items[key] == 'undefined') return null;
64 var used_key = this._remove_key(key);
65 this._keys.push(used_key);
66 return this._items[key];
67 },
68
69 remove_least: function() {
70 /* Manually remove the least recently used item. */
71 if (!this.is_empty()) this.remove(this._keys[0]);
72 },
73
74 remove: function(key) {
75 /* Remove an item by its key.
76 * If the item of ``key`` does not exist, returns null.
77 */
78 if (typeof this._items[key] == 'undefined') return null;
79 this._remove_key(key);
80 return this._remove_value(key);
81 },
82
83 _remove_key: function(key) {
84 /* Remove the ``key`` in ``this._keys``.
85 */
86 var i = $.inArray(key, this._keys);
87 return this._keys.splice(i, 1);
88 },
89
90 _remove_value: function(key) {
91 /* Remove the item in ``this._items``. */
92 var value = this._items[key];
93 delete this._items[key];
94 return value;
95 }
96
97 };
2008年7月2日
有备无患:pinyin-urlify - 根据汉字自动生成拼音 URL
根据文章标题中的英语单词自动生成文章的 URL 是一个很好的体验,比如标题为“Girl's death sparks riot in China”,会被转换成“girls-death-sparks-riot-china”这样与之对应的 URL。这样既可获得 Cool URIs,又便于用户记忆,同时也有利于 SEO,Blogger 和 Wordpress 也都支持类似体验。而中文的文章标题,也可以先将其中的汉字转化为拼音后再组成相应的 URL,比如标题为“俯卧撑”,可以被转换成“fu-wo-cheng”。
pinyin-urlify 是我这几天写的能根据汉字自动生成拼音 URL 的 Python 脚本。它可以将汉字映射成有英语字母组成的拼音,和多种西方语言的字母(拉丁语、希腊语、俄语等)映射成相近的英语字母。要说明的是,汉字-拼音的映射表是从 pyzh 项目中获得,而其他西方语言的映射表则是从 Django 项目中获得,特表感谢。
pinyin-urlify 支持自定义停用词列表(stop words)和保留词列表(reserved words)。停用词列表中的单词都会被过滤,不会出现在生成的 URL 中。生成的 URL 如果和保留词列表中的某一个单词匹配,则会被替换成默认的 URL 字符串。pinyin-urlify 可以指定 URL 的最大长度。不过,如果遇到有较多汉字组成的文章标题的话,实际生成的 URL 可能会比限定的最大长度小一些。我希望能在以后修正这一点。
使用示例
>>> from urlify import urlify
>>> urlify(u'三个俯卧撑引发的血案', default='blog_post',
... max_length=50, stop_words=[u'is', u'a', u'an'],
... reserved_words=[u'new', u'edit'])
u'san-ge-fu-wo-cheng-yin-fa-de-xue'
欢迎大家用 svn 检出 pinyin-urlify 的副本来使用。欢迎任何意见或建议 :-)
$ svn checkout http://pinyin-urlify.googlecode.com/svn/trunk/ pinyin-urlify
2008年6月21日
有备无患:在 GitHub 上用不同的 SSH 公钥部署多个应用
GitHub 是一个免费的基于 git 的开源软件仓库托管服务,包括 Ruby on Rails, Rspec,Prototype 在内的许多开源软件都在 GitHub 上架设了软件仓库。
如果你有两个或两个以上的 GitHub 仓库,并且想用不同的公钥/私钥来管理这些仓库的话,会遇到这样一个问题。当你 clone 第一个仓库的时候,git 会使用第一对公钥/私钥,这个时候仍然一切正常。可当你需要用另一对公钥/私钥来 clone 第二个仓库的时候,git 默认仍然会使用第一对公钥/私钥,然后 GitHub 就会返回一个授权错误。
要解决这个问题,首先需要在 ~/.ssh/ 目录下新建一个 config 文件,为不同的项目分别指定公钥/私钥
~/.ssh/config
Host project-1
HostName github.com
User git
IdentityFile /home/wyt/.ssh/id_rsa_project_1
Host project-2
HostName github.com
User git
IdentityFile /home/wyt/.ssh/id_rsa_project_2
然后用 ssh-keygen 创建对应的公钥/私钥。
$ ls -l ~/.ssh
total 24
-rw-r--r-- 1 wyt wyt 244 2008-06-21 13:18 config
-rw------- 1 wyt wyt 1675 2008-06-21 13:04 id_rsa_project_1
-rw-r--r-- 1 wyt wyt 392 2008-06-21 13:04 id_rsa_project_1.pub
-rw------- 1 wyt wyt 1675 2008-05-20 11:24 id_rsa_project_2
-rw-r--r-- 1 wyt wyt 392 2008-05-20 11:24 id_rsa_project_2.pub
-rw-r--r-- 1 wyt wyt 802 2008-05-24 22:44 known_hosts
将公钥上传到 GitHub 上之后,就可以用下面的指令 clone 相应的软件仓库了
$ git clone git@project-1:username/project-1.git
$ git clone git@project-2:username/project-2.git
参考页面
2008年6月17日
2008年5月27日
有备无患:增强豆瓣收藏对话框的 Greasemonkey 脚本
经常使用 del.icio.us 的人,一定也很熟悉 del.icio.us 的跟随键盘输入自动提示标签(Tag Suggest)的功能。它为用户管理大量的标签带来方便,比如不必去费神记忆单词的拼写,避免为同一语义定义多个标签等。这是靠我们的大脑来记忆管理,或者类似豆瓣只提供10个最常用的标签,不能做到的。
这个 Greasemonkey 脚本的主要功能,是为豆瓣收藏对话框提供类似 del.icio.us 的标签提示功能。另外还重新布置了读过/在读/想读等按钮的布局,并添加了删除收藏的按钮。
目前脚本的版本为0.1,欢迎试用。有什么建议或意见,欢迎留言:-)
功能
- 类似 del.icio.us 的随输入自动提示标签(Tag Suggest)
- 重新安排了读过/在读/想读等按钮的布局,保证 UI 路径的一致性
- 增加了删除收藏的按钮
下载
2008-05-31 UPDATE
脚本更新至0.2,有下列更新
- 增加了对个人收藏页,标签页,豆瓣猜,TOP250等页面中的收藏对话框的支持
- 修正了之前脚本中已知的 BUG
2008-09-18 UPDATE
脚本更新至0.3
- 支持更新后的收藏对话框
默认展开标签- 支持豆瓣全域的收藏对话框
2008年5月19日
有备无患:在 Gentoo 上部署 Git + Gitosis 服务器的笔记
Gitosis 是 Tommi Virtanen 为了更方便和安全的辅助 Git 架设和管理软件版本库 (Software Repository) 而开发的工具软件。虽然 Git 本身也提供 git-daemon 以架设版本库,但在用户访问控制上做的并不严格。而 Gitosis 允许单个用户帐号管理多个版本库,使用 SSH keys 管理用户认证,不需要 shell 帐号就可以解决多用户访问集中版本库的问题。
需要说明的是,我使用的 Gitosis 是 robbat2 为了部署新的 Gentoo Overlays 而开发的 Gentoo 分支版本 (Gentoo Fork Version),和上游版本 (Origin Upstream Version) 相比有如下不同:
- 支持 git+ssh://HOST/REPO 风格的相对路径;
- 支持以命令行参数方式导入 keys,如 gitosis-init --adminkey=FILE --adminname=STRING;
- 智能处理 SSH keys,支持 SSH1 和 SSH2 keys;
- 将上游版本默认的目录权限 0750 更改为 0755,以便用 nobody:nobody 运行 git-daemon。
这篇文章详细记录了我在本地安装和部署 Git + Gitosis 的过程和遇到的问题,希望为大家提供一些参考。欢迎 Fix-Me :-)
安装 Git + Gitosis
安装 git 和 gitosis-gentoo。安装完后,Gentoo 会自动添加 git 用户和组,并将版本库的主目录设为 /var/spool/gitosis/repositories。# echo ">=dev-util/git-1.5.5" >> /etc/portage/package.keywords
# echo ">=dev-util/gitosis-gentoo-0.2_p20080203" >> /etc/portage/package.keywords
# emerge -av git gitosis-gentoo
如果你的主机上没有装 SSH 的话,还要安装 openssh, 以及 keychain 用来管理 ssh-agents(可选)。
# emerge -av openssh keychain
安装完后,启动 sshd,并加入默认启动。
# /etc/init.d/sshd start
# rc-update add sshd default
配置 Gitosis
首先生成一个 ssh key。$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/wyt/.ssh/id_rsa):(回车)
Created directory '/home/wyt/.ssh'.
Enter passphrase (empty for no passphrase):(直接回车)
Enter same passphrase again: (直接回车)
Your identification has been saved in /home/wyt/.ssh/id_rsa.
Your public key has been saved in /home/wyt/.ssh/id_rsa.pub.
The key fingerprint is:
dd:33:cc:99:33:00:66:77:ff:00:bb:99:00:22:55:88 wyt@gentoo
我把 ssh key 保存在默认的 ~/.ssh/id_rsa,你也可以选择其他地方。接下来把 id_rsa.pub 上传到服务器。其中的root@gentoo,root 是根用户名,gentoo 是主机名,可以从 /etc/conf.d/hostname 得知。
$ cat /etc/conf.d/hostname
hostname="gentoo"
$ scp ~/.ssh/id_rsa.pub root@gentoo:
Password: (输入 root 帐户密码)
id_rsa.pub 100% 392 0.








