在Vim中发布博客内容到WordPress

Vim中发布博客的想法开始只是因为好玩。有不少VIM的插件可以支持从VIM中直接发送内容到Wordpress-我使用的博客软件。可是真的要用起来却真的是大费周则。如果只是将文字发送到博客网站那确实不难,可是显示出来的博客内容都是没有任何格式的平淡文字。当然我们可以在VIM中编辑HTML然后发送,可是这也太... 手工编辑HTML的傻事当然不能做。那有什么可以容易编辑又能获得格式化文字的方法呢?自然想到了正在学习中的 reStructuredText (简称reST)。reST实际上是轻量级结构化文本格式的一种,类似的还有很多,比如: Markdown, Deplate, txt2tags (txt2tags相对简单易学,比较喜欢,我还翻译了 txt2tags的使用技巧 可惜没有相应的Wordpress插件支持)。使用reST的好处是:它是Python语言官方文档使用的格式,我也恰好正在学习Python。而reST与 Sphinx 的配合也使文档的编辑工作变得有趣味起来。幸运的是,Wordpress有支持这种格式的插件,不幸的是,为了使用这个插件耗费了我不老少的时间,幸运的是,你不必了。以下是具体的操作步骤:

安装从Vim中发布内容到Wordpress的Vim插件

这类插件有: Vimpress, Vimblog, Blogit

这些插件的原理是通过Wordpress提供的XML-RPC界面与之通讯,可以在Vim中显示博客列表,编辑,删除,发布博客等等。

Vimblog的插件使用Ruby写的,所以你的Vim必须是已经编译了Ruby支持才可以使用该插件,你可以在Vim中输入:

:version

来查看你的Vim是否符合要求。Vimblog插件的发布时间好象有点问题。Vimpress 和 Blogit 都是用 Python 编写的,所以你的Vim要支持Python。个人认为Blogit的代码写的比较漂亮些。

安装很简单,到上述的网站下载插件,然后拷贝到 ~/.vim 目录下解压缩,或者直接将解开来的"xxx.vim"插件丢到 ~/.vim/plugin 目录下即可。但是在Vim的网站上的Blogit插件好象打包有问题,没法解开。我已经汇报作者,不知道解决没有。你可以到它的版本库下载(没错,只有那一个文件)。然后用编辑器打开这个脚本文件,找到下面的内容,替换成你的配置即可:

blog_username = 'user'
blog_password = 'passwd'
blog_url = 'http://example.com/xmlrpc.php'

更新 : 你可以使用下面我修改的插件,增加了对 more 分页符的支持,增加了Slug支持。blogit.vim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
" Copyright (C) 2009 Romain Bignon
"
" This program is free software; you can redistribute it and/or modify
" it under the terms of the GNU General Public License as published by
" the Free Software Foundation, version 3 of the License.
"
" This program is distributed in the hope that it will be useful,
" but WITHOUT ANY WARRANTY; without even the implied warranty of
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
" GNU General Public License for more details.
"
" You should have received a copy of the GNU General Public License
" along with this program; if not, write to the Free Software
" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"
" Maintainer:   Romain Bignon
" URL:          http://symlink.me/wiki/blogit
" Version:      1.0.1
" Last Change:  2009 April 11
"
" Commands :
" ":Blogit ls"
"   Lists all articles in the blog
" ":Blogit new"
"   Opens page to write new article
" ":Blogit edit <id>"
"   Opens the article <id> for edition
" ":Blogit commit"
"   Saves the article to the blog
" ":Blogit push"
"   Publish article
" ":Blogit unpush"
"   Unpublish article
" ":Blogit rm <id>"
"   Remove an article
" ":Blogit categories"
"   Show categories list
" ":Blogit help"
"   Display help
"
" Configuration :
"   Edit the "Settings" section
"
" Usage :
"   Just fill in the blanks, do not modify the highlighted parts and everything
"   should be ok.
"
" vim: set et softtabstop=4 cinoptions=4 shiftwidth=4 ts=4 ai

command! -nargs=+ Blogit exec('py blogit.command(<f-args>)')

python <<EOF
# -*- coding: utf-8 -*-
import vim, xmlrpclib, sys, re, time, datetime
from xmlrpclib import DateTime, Fault
from types import MethodType

#####################
#      Settings     #
#####################

blog_username = 'user'
blog_password = 'passwd'
blog_url = 'http://example.com/xmlrpc.php'
# enabled if blog has the tag plugin:
have_tags = True

#####################
# Do not edit below #
#####################

class BlogIt:

    def __init__(self):
        self.client = xmlrpclib.ServerProxy(blog_url)
        self.current_post = None
        self.haveTags = have_tags

    def command(self, command, *args):
        commands = self.getMethods('command_')
        if not command in commands:
            sys.stderr.write("No such command: %s" % command)
        try:
            commands[command](*args)
        except TypeError, e:
            try:
                sys.stderr.write("Command %s takes %s arguments" % (command, int(str(e).split(' ')[3]) - 1))
            except:
                sys.stderr.write('%s' % e)

    def command_help(self):
        sys.stdout.write("Available commands:\n")
        sys.stdout.write("   Blogit ls              list all posts\n")
        sys.stdout.write("   Blogit new             create a new post\n")
        sys.stdout.write("   Blogit edit <id>       edit a post\n")
        sys.stdout.write("   Blogit commit          commit current post\n")
        sys.stdout.write("   Blogit push            publish post\n")
        sys.stdout.write("   Blogit unpush          unpublish post\n")
        sys.stdout.write("   Blogit rm <id>         remove a post\n")
        sys.stdout.write("   Blogit categories      list categories\n")
        sys.stdout.write("   Blogit help            display this notice\n")

    def command_ls(self):
        try:
            allposts = self.client.metaWeblog.getRecentPosts('',blog_username, blog_password)
            if not allposts:
                sys.stderr.write("There isn't any post")
                return

            formatter = '%%%dd\t%%s\t%%s' % len(allposts[0]['postid'])
            del vim.current.buffer[:]
            vim.command("set syntax=blogsyntax")
            self.current_post = None
            vim.current.buffer[0] = "ID\tDate             \tTitle"
            for p in allposts:
                vim.current.buffer.append(formatter % (int(p['postid']), p['date_created_gmt'], p['title'].encode('utf-8')))
                vim.command('set nomodified')
            vim.current.window.cursor = (2, 0)
            vim.command('map <enter> :py blogit.list_edit()<cr>')
        except Exception, err:
            sys.stderr.write("An error has occured: %s" % err)

    def list_edit(self):
        row,col = vim.current.window.cursor
        id = vim.current.buffer[row-1].split()[0]
        self.command('edit', int(id))

    def command_edit(self, id):
        try:
            id = int(id)
        except ValueError:
            sys.stderr.write("'id' must be an integer value.")
            return

        try:
            post = self.client.metaWeblog.getPost(id, blog_username, blog_password)
            self.display_post(post)
        except Fault, e:
            sys.stderr.write(e.faultString)

    def command_new(self):
        username = self.client.blogger.getUserInfo('', blog_username, blog_password)['firstname']
        self.display_post({'wp_author_display_name': username,
                           'postid': '',
                           'title': '',
                           'wp_slug': '',
                           'categories': '',
                           'mt_keywords': '',
                           'date_created_gmt': '',
                           'description': '',
                           'mt_text_more': '',
                           'post_status': 'draft',
                           })

    def display_post(self, post):
        vim.command("set ft=rst")
        del vim.current.buffer[:]
        vim.current.buffer[0] = 'From: %s' % post['wp_author_display_name'].encode('utf-8')
        vim.current.buffer.append('Post-Id: %s' % post['postid'])
        vim.current.buffer.append('Subject: %s' % post['title'].encode('utf-8'))
        vim.current.buffer.append('Slug: %s' % post['wp_slug'].encode('utf-8'))
        vim.current.buffer.append('Categories: %s' % ",".join(post["categories"]).encode("utf-8"))
        if self.haveTags:
            vim.current.buffer.append('Tags: %s' % post["mt_keywords"].encode("utf-8"))
        vim.current.buffer.append('Date: %s' % post['date_created_gmt'])
        vim.current.buffer.append('')
        content = post["description"].encode("utf-8")
        content_more = post["mt_text_more"].encode("utf-8")
        if content_more:
          content = content + "<!--more-->" + content_more
        for line in content.split('\n'):
            vim.current.buffer.append(line)

        vim.current.window.cursor = (8, 0)
        vim.command('set nomodified')
        vim.command('set textwidth=0')
        self.current_post = post

    def getMeta(self, name):
        n = self.getLine(name)
        if not n:
            return ''

        r = re.compile('^%s: (.*)' % name)
        m = r.match(vim.current.buffer[n])
        if m:
            return m.group(1)

        return ''

    def getLine(self, name):
        r = re.compile('^%s: (.*)' % name)
        for n, line in enumerate(vim.current.buffer):
            if line == '':
                return 0
            m = r.match(line)
            if m:
                return n

        return 0

    def command_commit(self):
        if self.current_post is None:
            sys.stderr.write("Not editing any post.")
            return

        push = 0
        if self.current_post['post_status'] == 'publish':
            push = 1
        self.sendArticle(push=push)

    def command_push(self):
        if self.current_post is None:
            sys.stderr.write("Not editing any post.")
            return
        self.sendArticle(push=1)

    def command_unpush(self):
        if self.current_post is None:
            sys.stderr.write("Not editing any post.")
            return
        self.sendArticle(push=0)

    def nowstr(self):
        return time.strftime('%Y%m%dT%H:%M:%S', time.localtime(time.mktime(datetime.datetime.utcnow().timetuple())))

    def sendArticle(self, push=0):
        try:
            vim.command('set nomodified')
            start_text = 0
            for line in vim.current.buffer:
                start_text += 1
                if line == '':
                    break

            post = self.current_post
            post['title'] = self.getMeta('Subject')
            post['wp_slug'] = self.getMeta('Slug')
            post['wp_author_display_name'] = self.getMeta('From')
            post['categories'] = self.getMeta('Categories').split(',')
            if self.haveTags:
                post['mt_keywords'] = self.getMeta('Tags')
            post['description'] = '\n'.join(vim.current.buffer[start_text:])
            post['mt_text_more'] = ''

            datetime = self.getMeta('Date')
            if datetime != '' and (self.current_post['post_status'] != 'draft' or \
                                   DateTime(datetime) > DateTime(self.nowstr())):
                post['date_created_gmt'] = DateTime(datetime)
            else:
                post['date_created_gmt'] = DateTime(self.nowstr())

            if push:
                post['post_status'] = 'publish'
            else:
                post['post_status'] = 'draft'

            strid = self.getMeta('Post-Id')

            if strid == '':
                strid = self.client.metaWeblog.newPost('', blog_username,
                                                       blog_password, post, push)
            else:
                self.client.metaWeblog.editPost(strid, blog_username,
                                                blog_password, post, push)

            self.display_post(self.client.metaWeblog.getPost(strid, blog_username, blog_password))
        except Fault, e:
            sys.stderr.write(e.faultString)

    def command_rm(self, id):
        try:
            id = int(id)
        except ValueError:
            sys.stderr.write("'id' must be an integer value.")
            return

        try:
            self.client.metaWeblog.deletePost('', id, blog_username, blog_password)
        except Fault, e:
            sys.stderr.write(e.faultString)
            return
        sys.stdout.write('Article removed')
        if self.current_post and int(self.current_post['postid']) == int(id):
            del vim.current.buffer[:]
            self.current_post = None

    def command_categories(self):
        cats = self.client.wp.getCategories('', blog_username, blog_password)
        sys.stdout.write('Categories:\n')
        for cat in cats:
            sys.stdout.write('  %s\n' % cat['categoryName'])

    def getMethods(self, prefix):
        services = {}
        for attrname in dir(self):
            if not attrname.startswith(prefix):
                continue
            attr = getattr(self, attrname)
            if not isinstance(attr, MethodType):
                continue
            name = attrname[len(prefix):]
            services[name] = attr
        return services

blogit = BlogIt()

安装好上述插件后,启动Vim,在cmd模式输入下面的命令以了解插件的用法:

:Blogit help

你现在已经可以在Vim中与Wordpress通讯了。如果要求不高,这样就能在Vim中发博客了。如果你跟我一样喜欢折腾,那咱接着练。

安装reST的Wordpress插件

安装此插件的目的是让Wordpress“认识”reST格式的文本。其原理是通过结构文本分析系统 Docutils 将reST格式的文本转为HTML。所以我们需要在Wordpress的服务器上安装Docutils,要知道Docutils是用Python语言写的一个模块,所以要确保已经安装了Python。

安装 Docutils 有很多方法,最简单的是使用 easy_install

$ easy_install docutils

在Debian类的Linux比如Ubuntu上也可用用下面的方法:

$ aptitude install python-docutils

安装好后输入下面的命令:

$ type rst2html.py

如果安装正确的话会返回类似下面的信息:

$ rst2html.py is /usr/local/bin/rst2html.py

记录一下上面的rst2html.py的路径,一会儿要用到。接下来安装插件。插件的原始位置在 https://code.launchpad.net/rest-wordpress 但是,插件在处理“阅读更多(more)“标记有问题。 你可以拷贝/粘贴下面我修改过的代码:rest.php

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
<?php
/*
Plugin Name: reStructuredText
Version: 1.2
Plugin URI: https://launchpad.net/rest-wordpress
Description: This is a simple wrapper for <a href="http://docutils.sf.net/">Docutils' reStructuredText</a>, also known as reST. If you use this plugin you should disable all other markup plugins. Be sure to turn off the "WordPress should correct invalidly nested XHTML automatically" option in your Writing options.
Author: Matthew Scott
Author URI: http://goldenspud.com/
*/

// Set this to the path of rst2html.py
$rst2html = "/usr/local/bin/rst2html";

// Set this to true to use pipes instead of temporary files.
$usepipes = true;

// Set this to the location to store temporary files if $usepipes is
// not set to true.
$tmpdir = "/tmp/rest_render";

// Change this if you'd rather use a non-standard mode-line for recognizing
// reStructuredText formatting.  (Not recommended).
$modeline = "-*- mode: rst -*-";

// Change these to the options you prefer based on your personal preferences.
// See the rst2html man page for detailed information on available options.
$rst2html_options = ''
    . '--no-toc-backlinks '
    . '--no-doc-title '
    . '--no-generator '
    . '--no-source-link '
    . '--no-footnote-backlinks '
    . '--rfc-references '
    . '--initial-header-level=3 '
    . '--footnote-references="superscript" '
    ;

/**
 * Create a directory structure recursively
 *
 * @author      Aidan Lister <aidan@php.net>
 * @version     1.0.0
 * @param       string   $pathname    The directory structure to create
 * @return      bool     Returns TRUE on success, FALSE on failure
 */

function mkdirr($pathname, $mode = null)
{
  // Check if directory already exists
  if (is_dir($pathname) || empty($pathname)) {
    return true;
  }

  // Ensure a file does not already exist with the same name
  if (is_file($pathname)) {
    trigger_error('mkdirr() File exists', E_USER_WARNING);
    return false;
  }

  // Crawl up the directory tree
  $next_pathname = substr($pathname, 0, strrpos($pathname, DIRECTORY_SEPARATOR));
  if (mkdirr($next_pathname, $mode)) {
    if (!file_exists($pathname)) {
      return mkdir($pathname, $mode);
    }
  }

  return false;
}


/**
 * Convert reStructuredText markup to HTML.
 *
 * @param   string  $text   The text in reStructuredText format.
 * @return  string  Returns reStructuredText rendered as HTML.
 */
function reST($text) {
  global $rst2html;
  global $rst2html_options;
  global $usepipes;
  global $tmpdir;

  // Look for cached rendering and use it if available.
  $hash = md5($text);
  $rval = wp_cache_get($hash, 'rest');
  if (false !== $rval) {
    return $rval;
  }

  // Look for mode-line.  If it exists, use reST.  Otherwise, use wpautop.
  $pos = strpos($text, "-*- mode: rst -*-");
  if ($pos === false) {
    // No modeline.
    $rval = wpautop($text);
  } else {
    // rst modeline found.

    // TODO: Handle nextpage tags.

    if ($usepipes === true) {
      $descriptorspec = array(0 => array("pipe", "r"),
                              1 => array("pipe", "w"),
                              );
      $execstr = $rst2html . ' ' . $rst2html_options;
      $process = proc_open($execstr, $descriptorspec, $pipes);
      if (!is_resource($process)) {
        return "ERROR";
      }
      $txtfile = $pipes[0];
      fwrite($txtfile, $text);
      fflush($txtfile);
      fclose($txtfile);
      $rst = '';
      while (!feof($pipes[1])) {
        $rst .= fgets($pipes[1]);
      }
      fclose($pipes[1]);
      $rval = proc_close($process);
    } else {
      mkdirr($tmpdir, 0700);
      $filename = $tmpdir . '/' . rand(1,16384) . '-rest.txt';
      $txtfile = fopen($filename, 'w');
      fwrite($txtfile, $text);
      fclose($txtfile);
      $execstr = $rst2html . ' ' . $rst2html_options . ' ' . $filename;
      $rst = shell_exec($execstr);
      unlink($filename);
    }

    // Get rid of wrapping body tags.
    $rststart = strpos($rst, '<body>') + strlen('<body>');
    $rststop = strpos($rst, '</body>');
    $rst = substr($rst, $rststart, $rststop - $rststart);

    // TODO: Only use <div id="content" ...> node?

    // Uncomment more target tags.
    $pattern = ',<!-- <a (href|id)="([^#]+#more[^"]+)"([^>]*)>(.*)</a> -->,';
    $replacement = '<a \1="\2"\3>\4</a>';
    $rst = preg_replace($pattern, $replacement, $rst);

    // Uncomment more target tags.
    $pattern = ',<!-- <span id="more([^"]+)"([^>]*)></span> -->,';
    $replacement = '<span id="more\1"\2></span>';
    $rst = preg_replace($pattern, $replacement, $rst);

    $rval = $rst;
  }

  // Save rendered HTML into cache to speed up future access.
  wp_cache_set($hash, $rval, 'rest', 60 * 60 * 24);
  return $rval;
}


// Modify filter chains to use reST.

remove_filter('the_content', 'wpautop');
remove_filter('the_excerpt', 'wpautop');

remove_filter('the_content', 'wptexturize');
remove_filter('the_excerpt', 'wptexturize');

add_filter('the_content', 'reST');
add_filter('the_excerpt', 'reST');

?>

将插件中的下面这行内容按你之前记录下的路径信息修改,保存后将其放到Wordpress的Plugin目录。然后到Wordpress的后台管理界面激活此插件,即可。

$rst2html = '/usr/local/bin/rst2html.py'

注意的是:你编辑的reST文本的第一行必须是:

.. -*- mode: rst -*-

这是所谓的“模式行”用以通知Wordpre对以下的文本按照reST的格式进行处理。

下面这个是reST格式的"阅读更多(more)"分页标记:

.. <!--more-->

到这里,你已经可以在Vim中向Wordpress发送reST格式的文本,Wordpress插件解析reST,然后就能按要求显示丰富格式的内容了。为了使自己在Vim中编辑reST不至于太过沉闷,也为了防止语法出错,我们还可以:

安装Vim的reST语法高亮插件

Vim7.1中本来就有支持reST格式的语法加亮插件。将其拷贝到Vim的本地配置目录即可:

$ cp /usr/share/vim/vim71/syntax/rst.vim ~/.vim/syntax/

前提是你已经为Vim设置了 Syntax ON Filetype ON 。好了,你现在编辑reST文本时,五颜六色,还真解闷。我们还能干点什么呢?

博客内容中的代码语法加亮

在使用Vim编辑博客之前,倒是安装了 WP-Syntax 这种语法加亮插件。我只要在文章中添加一个特殊的HTML标记,就可以达到目的。可是现在用的是reST,以前的标记不再有效了。Google了一下,知道reST文本可以通过 pygments 来实现代码语法高亮。pygment也是一个Python模块,可以用 easy_install 来安装:

$ easy_install pygments

在Debian类Linux比如:Ubuntu上也可以这样安装:

$ apptitude install python-pygments

把下面的代码拷贝下来,保存为文件 rst2html.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env python2.5
# --*-- encoding: utf-8 --*--


# Options
# ~~~~~~~

# Set to True if you want inline CSS styles instead of classes
INLINESTYLES = True

from pygments.formatters import HtmlFormatter

# The default formatter
DEFAULT = HtmlFormatter(noclasses=INLINESTYLES)

# Add name -> formatter pairs for every variant you want to use
VARIANTS = {
    'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
}


from docutils import nodes
from docutils.parsers.rst import directives

from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer

def pygments_directive(name, arguments, options, content, lineno,
                       content_offset, block_text, state, state_machine):
    try:
        lexer = get_lexer_by_name(arguments[0])
    except ValueError:
        # no lexer found - use the text one instead of an exception
        lexer = TextLexer()
    # take an arbitrary option if more than one is given
    formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
    parsed = highlight(u'\n'.join(content), lexer, formatter)
    parsed = '<div class="codeblock">%s</div>' % parsed
    return [nodes.raw('', parsed, format='html')]

pygments_directive.arguments = (1, 0, 1)
pygments_directive.content = 1
pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])

directives.register_directive('code-block', pygments_directive)


"""
A minimal front end to the Docutils Publisher, producing HTML.
"""

try:
    import locale
    locale.setlocale(locale.LC_ALL, '')
except:
    pass

from docutils.core import publish_cmdline, default_description


description = ('Generates (X)HTML documents from standalone reStructuredText '
               'sources.  ' + default_description)

publish_cmdline(writer_name='html', description=description)

将保存的文件 rsth2tml.py 放到一个目录下比如: /home/USER/bin, 并且将该文件的属性设置为可执行:

$ sudo chmod a+x rst2html.py

最后按照你保存 rst2html.py 的路径修改之前提到的Wordpress插件中的相应位置(参见此处)。这样在解析reST文本的时候就能根据你的标记来提供语法加亮了。标记格式如下:

..code-block:: python
  :linenos:

  print "hello world!"
  print "syntax hilight is cool"

显示结果如下:

1
2
print "hello world!"
print "syntax hilight is cool"

将其中的python替换为相应的语言, :linenos: 是可选项,用于在代码前添加行号。

总结

一通的折腾,从Vim中发布博客终于基本可行。自己总结几点在Vim中发布博客的好处:

  1. 可以熟悉Vim的使用
  2. 可以熟悉reST这种格式
  3. 每篇博客都有一个漂亮的reST格式的本地拷贝,方便未来文本的合并,整理,转换。

目前,尚未实现的是在博客中添加附件,包括,图片,视频,文件等。也许轮到你告诉我怎么弄了。

本文rst原文

15 Responses to “在Vim中发布博客内容到WordPress”

  1. xiaozhang 说道:

    A very nice article .

  2. 朱涛 说道:

    图片等可能需要你使用脚本先上传到某个service上,然后使用reST的语法加上.

    其实我个人也是使用reST和VIM来写博客的,不过,是直接转换rst2html然后粘贴到我的BSP的editor里. 多媒体文件手动处理.

  3. digitalsatori 说道:

    你可以试试最新版的blogit.vim, 已经与我在本文中提到的blogit有了很大的改进,支持多个blog在vim中的发布,也可以通过pandoc实现在本地将rst转换为html然后通过xml-rpc界面上传blog. 这样就无需在Blog端安装转换rst的插件了。

    但是仍然不知道如何才能较好的实现“语法加亮”,所以目前我还是用在上文中提到的方法发布博客。

  4. helloning 说道:

    请教一下,我装了那个php插件,怎么提交之后在wordpress页面,直接显示的还是rest的语法文本,而不是结果。如显示*emphasis*,还不是显示一个斜体emphasis的

  5. helloning 说道:

    追加上条评论: 我的wordpress是装在自己电脑上的, 平时用的都还好用.

    关于那个restructuredText, 我服务器上用vim写好, 再用rst2html.py去转也是好用的. 用blogit读, 写, 修改也都是好用的. 就是不知道为什么blogit不支持中文. 还有一个就是wordpress装了rest.php插件后, 还是不识别restructText, 不知道为什么?

    麻烦能把本文的restructText源码给公布一下吗, 谢谢.

  6. digitalsatori 说道:

    rest.php中的$rst2html变量有没有设置为rst2html.py所在的路径

    不支持中文是指什么?你用的是否本文中贴出的blogit?

    原文的rst代码已附在文章末尾。

  7. helloning 说道:

    再次追加评论:
    用blogit和vimpress可以向wordpress发送restructureText了,以前错在理解错误,上文中提到.. — mode: rst –必须是文章开头。而实际应是-*- mode: rst -*-。

    现在的还有两个问题没有解决,望版主赐教。
    一是:有中文时,blogit提交不了。用vimpress可以,但vimpress不像blogit像样能对restructureText语法高亮,请问vim应该怎么设?或让blogit支持中文

    二是:我的restructureText转成html后,h1, h2, …标题不像版主这样由个包着。在rest.php 里去掉. ‘–no-toc-backlinks ‘可以有包着标题,但却出来了一个目录列表。我看版主的css没有对目录列表块进行隐藏。请问您是怎么配置,让其不产生目录列表同时又保留标题本身是个链接的。(这样可以便于文中指向标题)

  8. helloning 说道:

    您好,刚看到你的回复,不好意思。连问了这么多。
    我用的是本文提供的blogit.vim,不过把它放在plugin后,启动vim就报错了,类似^M的错误。我用命令 :set ff=unix 之后错误消失了。
    不支持中文意思是。在vim里用:Blogit ls可以把文章列表读回来,里面有中文,这没问题。但修改文章或发布新文章时,只要文章中出现中文和文章分类里有中文,:Blogit commit就报好几个python的错误,提交不了。把中文全替换成英文后便可以提交。
    用vimpress无此问题,但vim在使用vimpress中没有显示rest的语法高亮。

    我看了您的.rst,发现两点不理解。我的.rst写法和你一样,第一句是.. -*- mode: rst -*- ,但转化成html后这行会出现在我的博文开头,而您的以形式出现,也就不会显示在浏览器里。

    我的标题和您的写法一样,但标题本身便不是链接,不像您的这样h1,2,3…6都由包着。难道是wordpress版本会有影响?docutils的版本会有影响?

  9. digitalsatori 说道:

    感谢指出文中模式行的书写错误,已改正。

    rest.php是通过模式行来识别上传的博文是否为rst格式。如果博文中的模式行与rest.php中指定的完全吻合(请注意模式行中的空格),那么rest.php会通过docutils将rst转为HTML, 同时因为模式行的起始位置有“..”标志,docutils会将其转为HTML的comment从而不在浏览器中显示出来。

    中文的问题,能否提供python的错误信息,具体看看是什么错误引起的。

    我在实际使用中为了实现脚注的返回链接将rest.php中的“. ‘–no-footnote-backlinks ‘”这一行注释掉了,看看这是否会影响小结标题链接的生成。

  10. helloning 说道:

    有个最新的问题请教。我发现用楼主所用代码高亮方法很好用,但有一个问题 ::
    如果代码中出现取地址符号&, 则转化后会乱掉。
    这应该是rest.php插件的问题。因为同一段代码用
    pygmentize -f html -O full -o test.html test.c来生成网页的话,
    取地址符号是能正确显示的。而嵌入到wordpress里则出乱了。

    请教楼主有什么好的解决办法没。

  11. 疯的男子 说道:

    你好,我安装的是最新版的blogit,根据作者的指示,在~/.vim/下建立passwords.vim,填上我的 wp账号,密码等。 然后:Blogit ls [blogname],(请问这个blogname是我的博客名?还是我的账号,或者是别的东西?)反正我把我的博客名和我的账号都试了下,提示: expected string or buffer!
    同时没看到列出我博客的文章啊.
    我输入 :py print 1 , gvim 返回的是1 ,wordpress后台也启用了xml-rpc协议啊。
    希望能指点下啊。

  12. digitalsatori 说道:

    最新版blogit.vim支持多Blog的文章发布。所以如果你设置了多个Blog, 你可以用Blogit ls blogname来向指定的Blog发送文章。你可以参见blogit的使用说明中关于多个Blog的设置方法,以及默认Blog Name的设置方法。
    如果你只有一个Blog, 那么就直接用Blogit ls就可以了,注意blogname是在[]中的,表示不是必需的。

    另外,要注意用最新的blogit.vim不一定能实现我在博文中说的,发布rst到wordpress并且保持语法加量。

  13. Kebot 说道:

    最近开始用vim,用的是MacVIM,不过在用blogit.vim和 Vimpress的时候都报错,我用的MacVIM感觉和python的版本有关系…

    唯一能用的还是vimblog,不过blog xxx, 命令倒是挺简短的

    不过相对 Ruby 还是会一点 Python …

    不过还是谢谢你介绍了这么多好的插件

  14. [...] 参考在Vim中发布博客内容到WordPress 这篇文章 tags: Mac, VIM older » [Soft]Audition For Mac » No Responses to "使用 VIM". Add a comment? or Follow comments by RSS? Be the first and share your thoughts! [...]

  15. lidong 说道:

    江湖同好,都是VIM, FREEBSD, Python 爱好者。

    我们加友情链接吧。

Leave a Response