使用markdown格式写Todo list

上周我完成了chrome的markdown-preview扩展的修改,使它支持markdown文件的实时预览功能。现在可以考虑用这个功能来做一些更酷的事情,比如说,写Todo list?

开始干

  • 首先我找到了一个叫topmarks的markdown样式文件,这个样式文件能给出一个漂亮的Todo list样式,我把它合并到了 修改后的markdown preview插件
  • 关注了一下markdown-preview原作者的github,他有计划增加一项功能,让用户可以自定义css文件,但是近期完成无望,所以我新增了一个topmarks的分支,采用新的样式
  • 在chrome的扩展中重新加载插件

效果图

如图,我采用vim编辑文件,右边摆上chrome浏览器预览,文件采用dropbox同步,保证我随时能更新这个文件。

\"效果图\"

关于topmarks的补充说明

topmarks样式采用了这些自定义标签

  • <t> 新任务
  • <d> 已完成的任务
  • <c> 已取消的任务
  • http, ical, mailto, file链接有不同的图标

markdown文件的编辑以及实时预览

之前看到有一款叫Mou的软件可以编辑markdown格式的文本内容并且实时预览,可是我比较喜欢用vim编辑器,所以就想着能不能用别的软件配合着来实现实时预览。

今天看到一个chrome的扩展叫markdown preview,它可以自动把md或markdown后缀的文本转换为html格式在chrome中查看,只是还欠缺一个自动刷新的功能。然后我看到它的源码已经放在github上,于是抓回来做了一些修改,加上了自动刷新的功能。

演示

尝试了一下在vim中编辑文件,然后在chrome中浏览,效果还不错,见下图:

\"效果图\"

更改后的扩展

我把修改后的markdown preview插件放在github上 , 已经尝试向原作者发起pull requests,要试用的话需要按下面几步操作:

  1. 把插件的代码clone回来 git clone git://github.com/volca/markdown-preview.git
  2. 开启chrome扩展的开发者模式,具体位置在窗口 -> 扩展程序
  3. 载入正在开发的扩展程序
  4. 允许访问文件网址勾选上

然后你就可以尝试用chrome浏览器打开markdown文件预览html,采用趁手的编辑器去修改它。

BTW

  • chrome下的插件写起来比想像的要简单,写javascript就是了
  • 现在这篇blog即是我用markdown格式写成的,用到了wordpress的markdown on save扩展。

Update

  • 测试把文件放在dropbox的Public下,修改之后的效果能够立刻在共享链接里看到效果,很完美

php的异步http请求类

基于上次写的关于php的libevent扩展的应用,我实现了一个异步的http请求类。

代码在github上:https://github.com/volca/AsyncHttpClient

使用示例

如下,在request的时候能够定义请求完成之后的callback函数。


 $base
);

for($i = 0; $i < 10; $i++) {
    $client = new AsyncHttpClient($uri, $config);
    $client->request(function($result) {
        echo "Result len:";
        echo strlen($result['response']);
        // parse response with Zend_Http_Response
        $response = Zend_Http_Response::fromString($result['response']);              
        echo $response->getBody();
        echo "\n";
    });
}

event_base_loop($base);
echo "done\n";

目前的实现比较初级,只做了get方法的封装,如果需要http上传或者post,还需要另外实现。另外这个类也需要php的libevent扩展

Update 2011.11.17

将这个类修改为继承自Zend_Http_Client,这样我可以少写一些代码,顺带也实现了文件http上传以及post(未经测试),代码中的使用示例也已经更新。

关于php的libevent扩展的应用

php有个libevent扩展,在一年前我曾经拿它实现了一个thrift socket server,虽然我没有把它放在正式的场合来使用,但是我觉得这个扩展应该可以有更广泛的用途,比如:

  • phpDaemon — 一个异步的服务器端开发框架.
  • tail – 用php实现类似unix下的tail命令行
  • ZeroMQ + libevent in PHP – 用php和ZeroMQ实现的一个事件驱动服务器端

我所想到的一个比较实用的使用场景是,在页面中利用libevent请求多个http接口来获得数据。若是在从前,一个可行的办法是利用curl_multi_exec来同时请求好几个接口,但是这个办法需要用一个do … while循环来完成请求,很是坑爹。那么看看采用libevent的例子:

代码实例 http.php

为了省事,这个php脚本仅仅是重复抓取一个网页5次,并且回调的逻辑我没怎么做处理,仅仅是echo出来而已,可以通过下面命令行来运行这个例子:

php http.php "www.baidu.com"

代码中的http_get($argv[1])这行虽然是靠一个命令行顺序执行,但是不会阻塞后面的代码,直接就进行下一次请求了。而且我们看看回调方法部分是不是很像用javascript调用ajax写的回调方法?这都是php 5.3中闭包的功劳。


event_set($event_fd, $fd, EV_WRITE | EV_PERSIST, function($fd, $events, $arg) {
    //回调方法,后续处理随意
    echo fread($fd, 4096);
    if(feof($fd)) {
        fclose($fd);
	event_base_loopexit($arg[1]);
	echo "done";
    }
}, array($event_fd, $base_fd));

想到更多

在mysqlnd,memcached…这些php扩展中,都已经有delay回调的实现,如果能好好利用,对性能提升岂不是有莫大的帮助?或者在libevent扩展的基础上,实现一个事件驱动的开发框架,也是可行的。

Update 2011.11.10

在这个代码的基础上实现了一个异步http请求的客户端

Update 2011.10.28

event_base_loop是会阻塞后续代码执行的,所以我调整了示例代码,使用同一个event_base,并且用stream_socket_client来进行异步连接,另外在/etc/hosts指定域名的ip会对执行速度有帮助。

实战three20的TTTableViewController自定义单元格

three20中的TTTableCaptionItem实际使用效果是,左侧是较小字体的标题,右侧是大号字体的文本,如下图所示

但是在实际使用中,我希望左侧的字体能变大,右侧字体变小,所以我参照wiki的介绍 在TTTableViewController定制单元格 来进行调整,按文中介绍,我需要实现:

  1. 自定义的tableItem
  2. 自定义的tableItemCell
  3. 自定义的datasource,以便支持新增的tableItem

但是考虑到three20的代码库中已经有个半成品的TTTableRightCaptionItem,所以我只需要在这个基础上加工一下。

实现TTTableTextCaptionItem

我将这个自定义的类命名为TTTableTextCaptionItem以示区别

TTTableTextCaptionItem

TTTableTextCaptionItem.h


#import 

@interface TTTableTextCaptionItem : TTTableRightCaptionItem {
}

@end

TTTableTextCaptionItem.m


#import "TTTableTextCaptionItem.h"

@implementation TTTableTextCaptionItem
@end

TTTableTextCaptionItemCell

TTTableTextCaptionItemCell.h


#import 

@interface TTTableTextCaptionItemCell : TTTableRightCaptionItemCell {
}
@end

TTTableTextCaptionItemCell.m


#import "TTTableTextCaptionItem.h"
#import "TTTableTextCaptionItemCell.h"

static const CGFloat kKeySpacing = 12;
static const CGFloat kKeyWidth = 75;

@implementation TTTableTextCaptionItemCell

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView


///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)layoutSubviews {
    [super layoutSubviews];
    
    self.detailTextLabel.frame = CGRectMake(kTableCellHPadding, kTableCellVPadding,
                                      kKeyWidth, self.textLabel.font.ttLineHeight);
    
    CGFloat valueWidth = self.contentView.width - (kTableCellHPadding*2 + kKeyWidth + kKeySpacing);
    CGFloat innerHeight = self.contentView.height - kTableCellVPadding*2;
    self.textLabel.frame = CGRectMake(kTableCellHPadding + kKeyWidth + kKeySpacing,
                                            kTableCellVPadding,
                                            valueWidth, innerHeight);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark TTTableViewCell


///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)setObject:(id)object {
    if (_item != object) {
        [super setObject:object];
        
        TTTableTextCaptionItem* item = object;
        self.textLabel.text = item.text;
        self.detailTextLabel.text = item.caption;
    }
}

@end

如何使用TTTableTextCaptionItem

首先实现一个自定义的DataSource,然后在后续代码中使用这个DataSource

#import "TTTableTextCaptionItemCell.h"
#import "TTTableTextCaptionItem.h"

@interface MyCustomDataSource : TTSectionedDataSource
@end

@implementation MyCustomDataSource

- (Class)tableView:(UITableView*)tableView cellClassForObject:(id)object {
    if ([object isKindOfClass:[TTTableTextCaptionItem class]]) {
        return [TTTableTextCaptionItemCell class];
    } else {
        return [super tableView:tableView cellClassForObject:object];
    }
}

@end

代码下载

我把代码放到github上,方便大家参考 TTTableTextCaptionItem.git

如何将TTURLRequest和OAuthConsumer搭配使用

TTURLRequest是three20开发框架提供的一个url请求类,它是NSURLRequest类的扩展,有如下优点:

  • post数据方便,只需要构建一个参数的dictionary就可以了,像get方法一样简单
  • 支持磁盘缓存,而NSURLRequest仅支持内存缓存
  • 因为TTTableViewController + TTURLRequestModel的存在,搭配使用效果良好

在ios的开发中我有时也用到了OAuthConsumer进行oauth授权,利用OAuthConsumer的fetcher类请求远程数据,所以我想TTURLRequest和OAuthConsumer能不能搭配使用,这样就能用上three20相关便利方法加载远程数据。于是做了点简单的测试,果然成功了:)大体思路是:

  • 先利用OAuthConsumer根据提交的参数计算出oauth的Authorization认证头
  • 把Authorization头加到TTURLRequest
  • 由TTURLRequest提交数据。

直接上代码说话:

计算Authorization头

利用OAuthConsumer计算出Authorization头,为之后的请求做准备


//start
OAConsumer * consumer = [[OAConsumer alloc] initWithKey:yourConsumerKey secret:yourSecret];
NSURL * url = [NSURL URLWithString:@"http://your-api-host/your-method"];
OAToken * authToken = [[OAToken alloc] initWithKey:yourAuthToken secret:yourAuthSecrent];
OAMutableURLRequest * request = [[OAMutableURLRequest alloc] initWithURL:url 
                                                                consumer:consumer 
                                                                   token:authToken 
                                                                   realm:nil 
                                                       signatureProvider:[[[OAPlaintextSignatureProvider alloc] init] autorelease]];
[request setHTTPMethod:@"POST"];

NSMutableArray * params = [NSMutableArray array];
OARequestParameter * p1 = [[OARequestParameter alloc] initWithName:@"param1" value:@"i'm param1"];
[params addObject:p1];

[request setParameters:params];
[request prepare];

TTDINFO(@"Authorization is %@", [request valueForHTTPHeaderField:@"Authorization"]);

使用TTURLRequest请求数据

将Authorization头附加到TTURLRequest,然后请求远程接口


TTURLRequest* req = [TTURLRequest requestWithURL:request.URL.absoluteString delegate:self];
req.response = [[[TTURLDataResponse alloc] init] autorelease];
req.httpMethod = @"POST";
req.cachePolicy = TTURLRequestCachePolicyNone;
[req setValue:[request valueForHTTPHeaderField:@"Authorization"] forHTTPHeaderField:@"Authorization"];
[req send];

这个方法不需要对TTURLRequest进行修改,简单有效。

php文档更新

php.net最近更新了php文档,比较有用的是新增的pman工具。pman是一个命令行小工具,方便查看php函数的本地帮助文档,但是不包含php.net的评论数据。简单的试用心得如下:

安装pman

使用传说中的pear来安装pman

sudo pear install doc.php.net/pman

如果pear版本比较老,需要先升级pear才可以继续

sudo pear upgrade pear

pman使用方法

pman的使用方法很傻瓜,比如我们想查看strlen的帮助信息:

pman strlen

帮助文本的内容是彩色的,能和chm版本的php帮助文档媲美。pman的详细使用帮助如下

#pman --help
man, version 1.6c

usage: man [-adfhktwW] [section] [-M path] [-P pager] [-S list]
	[-m system] [-p string] name ...

  a : find all matching entries
  c : do not use cat file
  d : print gobs of debugging information
  D : as for -d, but also display the pages
  f : same as whatis(1)
  h : print this help message
  k : same as apropos(1)
  K : search for a string in all pages
  t : use troff to format pages for printing
  w : print location of man page(s) that would be displayed
      (if no name given: print directories that would be searched)
  W : as for -w, but display filenames only

  C file   : use `file' as configuration file
  M path   : set search path for manual pages to `path'
  P pager  : use program `pager' to display pages
  S list   : colon separated section list
  m system : search for alternate system's man pages
  p string : string tells which preprocessors to run
               e - [n]eqn(1)   p - pic(1)    t - tbl(1)
               g - grap(1)     r - refer(1)  v - vgrind(1)

还有一个好处是在vim里查看php帮助信息更方便了,结合完美

:!pman strlen

git flow使用经验小记

我在半年前开始在公司内推广使用git flow,控制版本发布流程,到目前为止效果令人满意。

但是实际使用过程中有一些小小的意外流程,完全照搬git flow的模型不太容易处理好。好在git本身就很灵活,碰到问题基本上都有办法绕过去。下面是我总结的一些特例情况下的处理办法。

git-flow

测试/共享单独一个feature

有时候我们需要将一个feature独立测试,或者share给多人一块开发,那么可以将这个feature推到远程git库上,这可以利用git flow的publish功能搞定:

git flow feature publish my_cool_feature

这会将 feature/my_cool_feature 分支push到远程git库,多人开发或者单独测试毫无压力。

feature在development分支测试完成,准备release的时候有另外一个未经测试的feature合并进来

已经完成测试的development被未经测试的提交污染了,这时候可以先本地回滚development分支,然后再进行git flow的release流程,例如:

git checkout development 
git reset --hard 5cbadfe885d1eb514b3f07b3f269ca1a7f261e21   #假设测试通过的git rev是这个
git flow release start v1.0.1
git flow release finish v1.0.1

development上有个feature需要测试比较长时间,影响了一些耗时较短的feature发布

development分支上有个feature测试时间比较长一直释放不了,怎么办?—— 果断采用hotfix功能

git br -m feature/another_cool_feature hotfix/another_cool_feature

把耗时短的feature直接转换为hotfix,然后采用git flow的hotfix流程可以直接合并到master分支发布。

一个关于three20开发框架的wiki

我最近花了不少时间做iphone上的app,由于是刚刚接触objective-c,需要从头学习的东西比较多。我从使用facebook开源的three20开发框架来学习ios应用开发,这种方式不能说是最好的学习方式,但是我也从中获得了不少乐趣。

关于three20的中文资料不是太多,所以我搜集整理翻译了一些内容,放到了wiki上:

three20  wiki

本人才疏学浅,翻译的内容难免出错或者词不达意,欢迎批评指正。

用git部署php站点

在小站点上,直接用git来部署php代码相当方便,你的远程站点以及本地版本库都有一个版本控制,追踪问题或者回滚是很轻松的事情。下面介绍用git部署时的设置步骤

在远程服务器的设置

假定你需要部署的代码在/var/www/yoursite

cd /var/www/yoursite
git init .
git config receive.denyCurrentBranch ignore
git config --bool receive.denyNonFastForwards false
cd .git/hooks
wget https://gist.githubusercontent.com/volca/9482044/raw/344a590af350b997db3819fa21426dfe8bc140f4/post-update
chmod +x post-update

在本地git库中新增配置


[remote "prod"]
        url = your-ssh-username@your-host:/var/www/yoursite/

这样就算设置完成了。

如果你想把本地的代码推送到远程服务器,下面简单的步骤就可以做到

git pull
git push prod

注意事项

如果远程服务器上git的配置目录.git暴露在外部可访问的位置,请在web服务器上设置这个目录不可见。