6
17
2014
3

Google IP 可用性检测脚本

需要 Python 3.4+,一个参数用来选择测试搜索服务还是 GAE 服务。测试 GAE 服务的话需要先修改开头的两个变量。从标准输入读取 IP 地址或者 IP 段(形如 192.168.0.0/16)列表,每行一个。可用 IP 输出到标准输出。实时测试结果输出到标准错误。50 线程并发。

#!/usr/bin/env python3

import sys
from ipaddress import IPv4Network
import http.client as client
from concurrent.futures import ThreadPoolExecutor
import argparse
import ssl
import socket

# 先按自己的情况修改以下几行
APP_ID = 'your_id_here'
APP_PATH = '/fetch.py'

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations('/etc/ssl/certs/ca-certificates.crt')

class HTTPSConnection(client.HTTPSConnection):
  def __init__(self, *args, hostname=None, **kwargs):
    self._hostname = hostname
    super().__init__(*args, **kwargs)

  def connect(self):
    super(client.HTTPSConnection, self).connect()

    if self._tunnel_host:
      server_hostname = self._tunnel_host
    else:
      server_hostname = self._hostname or self.host
      sni_hostname = server_hostname if ssl.HAS_SNI else None

    self.sock = self._context.wrap_socket(self.sock,
                                          server_hostname=sni_hostname)
    if not self._context.check_hostname and self._check_hostname:
      try:
        ssl.match_hostname(self.sock.getpeercert(), server_hostname)
      except Exception:
        self.sock.shutdown(socket.SHUT_RDWR)
        self.sock.close()
        raise

def check_ip_p(ip, func):
  if func(ip):
    print(ip, flush=True)

def check_for_gae(ip):
  return _check(APP_ID + '.appspot.com', APP_PATH, ip)

def check_for_search(ip):
  return _check('www.google.com', '/', ip)

def _check(host, path, ip):
  for chance in range(1,-1,-1):
    try:
      conn = HTTPSConnection(
        ip, timeout = 5,
        context = context,
        hostname = host,
      )
      conn.request('GET', path, headers = {
        'Host': host,
      })
      response = conn.getresponse()
      if response.status < 400:
        print('GOOD:', ip, file=sys.stderr)
      else:
        raise Exception('HTTP Error %s %s' % (
          response.status, response.reason))
      return True
    except KeyboardInterrupt:
      raise
    except Exception as e:
      if isinstance(e, ssl.CertificateError):
        print('WARN: %s is not Google\'s!' % ip, file=sys.stderr)
        chance = 0
      if chance == 0:
        print('BAD :', ip, e, file=sys.stderr)
        return False
      else:
        print('RE  :', ip, e, file=sys.stderr)

def main():
  parser = argparse.ArgumentParser(description='Check Google IPs')
  parser.add_argument('service', choices=['search', 'gae'],
                      help='service to check')
  args = parser.parse_args()
  func = globals()['check_for_' + args.service]

  count = 0
  with ThreadPoolExecutor(max_workers=50) as executor:
    for l in sys.stdin:
      l = l.strip()
      if '/' in l:
        for ip in IPv4Network(l).hosts():
          executor.submit(check_ip_p, str(ip), func)
          count += 1
      else:
        executor.submit(check_ip_p, l, func)
        count += 1
  print('%d IP checked.' % count)

if __name__ == '__main__':
  main()

脚本下载地址


2014年9月3日重要更新:由于失误,之前的脚本没有检查 SSL/TLS 证书,所以将错误的 IP 认为是可用的。现已更新。

Category: python | Tags: python google 网络 中国特色
6
3
2014
7

照镜子为什么是左右颠倒,而不是上下颠倒?

这是个虽然简单但是很有意思的问题,以前我竟然未曾想过。后来看到「宇宙的心弦」上对这个问题的回答写得太模糊(什么叫「镜子里头脚的位置没变」?「位置没变」的定义是什么?),所以这里写一个尽可能精确描述的回答。首先,我们讨论最容易引起问题的那种情景,即人站立时正面照镜子。

首先定义几个概念。

左右。以你为原点,你的左手方向为左,右手方向为右。(你知道哪只手是左手吧?)

上下。站在地球表面,在空中静止释放一物体,由于重力,它会运动起来。其运动方向为下,反之为上。

前后。这个有点奇特。你面前站了一个人,背心对着你的时候,这是那人的面。面对着你的时候,你看到那人的面。我们的问题隐含了作为观察者的你,去看外界的像,而不是考察你自己,对吧?

让我们再定义一下坐标系。

右为 x 轴正方向,上为 y 轴正方向,由你(观察者)的后背指向你的胸前为 z 轴正方向。

作为观察者,这里有一个很明白的变换:你所认为「正」的像,其坐标要绕 y 轴旋转 180°,才能与你观察时使用的坐标系一致。让我解释得更清楚一些——

拿鼠标指针选中你,按一下Ctrl-D(如果你不是 Inkscape 用户,那就按一下Ctrl-C再按一下Ctrl-V)。现在有了你的一个像。但是你看不到它,因为它和你重合了。让我们把这个像向 z 轴正方向平衡一段距离,比如 2m,你再看看?哟,它怎么背对着你呀?不行,再原地转身 180°,这样才能看到正面不是?

忽略掉平移,让我们把这个变换记作 \(T_1\),有

$$ T_1 = \begin{bmatrix} \cos{\pi} & 0 & \sin{\pi} \\ 0 & 1 & 0 \\ -\sin{\pi} & 0 & \cos{\pi} \end{bmatrix} = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \end{bmatrix} $$

我们再来考察一下镜子做了什么。

想像镜子前有一个坐标系的三根轴,就是我们刚刚定义的那个。其中 z 轴正方向指向镜面。于是乎,镜子里的 x 轴与外边的 x 轴是平行且方向一致的。y 轴也是这样。但是 z 轴的位置没有改变,方向却反了过来,箭头对箭头了。所以,这种放置法,使得像与物体的 z 轴反了,\(z\) 变成了 \(-z\)。还是忽略掉平移,让我们把这个变换记作 \(T_2\)

$$ T_2 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \\ \end{bmatrix} $$

所以,最终作为观察者的你,看到的镜中的自己经历的变换是:

$$ T_1 T_2 = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \\ \end{bmatrix} = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix} $$

即最终的像的坐标中的 \(x\) 变成了 \(-x\),即左右颠倒。

让我们再考虑另一种情况试试。如果把镜子放在头顶上,看过去会是什么感觉呢?

这时候,y 轴一头扎进了镜子,于是,我们的第三个变换 \(T_3\) 为:

$$ T_3 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix} $$

乘一下,结果是:

$$ T_1 T_3 = \begin{bmatrix} -1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 & -1 \\ \end{bmatrix} $$

咦?这不是上下、左右、前后都颠倒了吗?找面位于天花板的镜子试试看。上下的确颠倒了不是么?再伸出左手试试,左右也和在面前的镜子里一样,也是颠倒的。可,前后感觉并没有颠倒啊?这是因为观察者和被观察对象位于同一垂直线上,感觉不一样了。仔细想想,天花板镜子里的像的前后确实与面前的镜子晨那个的前后是对着的,不是么?而我们定义后者没有颠倒,那么前者当然相对于后者是颠倒过了嘛。

个人拙见 =w=

Category: 未分类 | Tags: 物理
6
1
2014
11

给 Python 加上分数和十进制数字面量表示

听说 Ruby 支持分数字面量呢——

irb(main):001:0> 1/3r
=> (1/3)
irb(main):002:0> 1/3r+1/2r
=> (5/6)

可是在 Python 里只能这样子:

>>> from fractions import Fraction as R
>>> R(1, 3)
Fraction(1, 3)
>>> R(1, 3) + R(1, 2)
Fraction(5, 6)

学习《用 `accio` 代替 `import`:深入理解自举和 Python 语法》改改 Python 解释器,让它也支持分数字面量,应该会很有趣呢 =w=

去翻了一下 Grammar/Grammar 文件,有些失望。那里只有语法定义,没有词法定义呢。以为词法定义在另一个地方,所以找了找,无果。只有 C 代码。想到复数的 j 字尾,直接拿 ag 搜带引号的 "j"'j',最终确认它在 Parser/tokenizer.c 里。也就是说,Python 的词法分析是自己实现的。

在那个四千多行的tok_get函数里,有一部分代码在尝试解析一个数,也就是语法里的 NUMBER。照着解析复数的办法,把 d 后缀和 r 后缀也加进去:

diff -r bf65e7db066d Parser/tokenizer.c
--- a/Parser/tokenizer.c    Mon Apr 14 22:27:27 2014 -0400
+++ b/Parser/tokenizer.c    Fri May 30 20:12:07 2014 +0800
@@ -1528,6 +1528,10 @@
                 goto fraction;
             if (c == 'j' || c == 'J')
                 goto imaginary;
+            if (c == 'd' || c == 'D')
+                goto decimal;
+            if (c == 'r' || c == 'R')
+                goto rational;
             if (c == 'x' || c == 'X') {

                 /* Hex */
@@ -1621,6 +1625,12 @@
                     /* Imaginary part */
         imaginary:
                     c = tok_nextc(tok);
+                else if (c == 'd' || c == 'D')
+        decimal:
+                    c = tok_nextc(tok);
+                else if (c == 'r' || c == 'R')
+        rational:
+                    c = tok_nextc(tok);
             }
         }
         tok_backup(tok, c);

d 后缀是我给十进制数——就是会计里会用到的精确的十进制小数——准备的。

然后可以编译出来试试。这个 configure 命令是从 Arch 官方编译脚本里找的。

./configure --enable-shared --with-threads --with-computed-gotos --enable-ipv6 --with-valgrind --with-system-expat --with-dbmliborder=gdbm:ndbm --with-system-ffi --with-system-libmpdec --without-ensurepip
make

因为我不执行安装步骤,而又用了共享库,所以要这样子执行:

LD_LIBRARY_PATH=. ./python

试试看:

>>> 4d
ValueError: could not convert string to float: 4d

有效果,不报语法错了呢。

现在报ValueError,因为我还没告诉 Python 如何解析我新加的字面量表示呢。解析代码位于Python/ast.cparsenumber函数。最终的补丁如下:

diff -r bf65e7db066d Python/ast.c
--- a/Python/ast.c  Mon Apr 14 22:27:27 2014 -0400
+++ b/Python/ast.c  Fri May 30 20:12:07 2014 +0800
@@ -3650,12 +3650,29 @@
     long x;
     double dx;
     Py_complex compl;
-    int imflag;
+    char typeflag;
+    PyObject *mod, *type, *ret;

     assert(s != NULL);
     errno = 0;
     end = s + strlen(s) - 1;
-    imflag = *end == 'j' || *end == 'J';
+    switch(*end){
+        case 'j':
+        case 'J':
+            typeflag = 'j';
+            break;
+        case 'd':
+        case 'D':
+            typeflag = 'd';
+            break;
+        case 'r':
+        case 'R':
+            typeflag = 'r';
+            break;
+        default:
+            typeflag = 'i';
+    }
+
     if (s[0] == '0') {
         x = (long) PyOS_strtoul(s, (char **)&end, 0);
         if (x < 0 && errno == 0) {
@@ -3670,13 +3687,43 @@
         return PyLong_FromLong(x);
     }
     /* XXX Huge floats may silently fail */
-    if (imflag) {
+    if (typeflag == 'j') {
         compl.real = 0.;
         compl.imag = PyOS_string_to_double(s, (char **)&end, NULL);
         if (compl.imag == -1.0 && PyErr_Occurred())
             return NULL;
         return PyComplex_FromCComplex(compl);
     }
+    else if (typeflag == 'd') {
+      mod = PyImport_ImportModule("decimal");
+      if (mod == NULL)
+          return NULL;
+
+      type = PyObject_GetAttrString(mod, "Decimal");
+      if (type == NULL) {
+          Py_DECREF(mod);
+          return NULL;
+      }
+      ret = PyObject_CallFunction(type, "s#", s, strlen(s)-1);
+      Py_DECREF(type);
+      Py_DECREF(mod);
+      return ret;
+    }
+    else if (typeflag == 'r') {
+      mod = PyImport_ImportModule("fractions");
+      if (mod == NULL)
+          return NULL;
+
+      type = PyObject_GetAttrString(mod, "Fraction");
+      if (type == NULL) {
+          Py_DECREF(mod);
+          return NULL;
+      }
+      ret = PyObject_CallFunction(type, "s#", s, strlen(s)-1);
+      Py_DECREF(type);
+      Py_DECREF(mod);
+      return ret;
+    }
     else
     {
         dx = PyOS_string_to_double(s, NULL, NULL);

因为只是玩玩,所以不太认真,没仔细做错误处理;因为decimalfractions模块是从外部文件导入的,所以可能被覆盖掉,从而导致报错,并且这错误是无法通过异常处理捕获的。

不出问题的话,再次make之后,就可以开始玩了。不过在此之前,再多做几个补丁,让 Python 把分数和十进制数显示得简洁好看一点:

diff -r bf65e7db066d Lib/decimal.py
--- a/Lib/decimal.py    Mon Apr 14 22:27:27 2014 -0400
+++ b/Lib/decimal.py    Fri May 30 20:12:07 2014 +0800
@@ -1015,7 +1015,7 @@
     def __repr__(self):
         """Represents the number as an instance of Decimal."""
         # Invariant:  eval(repr(d)) == d
-        return "Decimal('%s')" % str(self)
+        return str(self) + 'd'

     def __str__(self, eng=False, context=None):
         """Return string representation of the number in scientific notation.
diff -r bf65e7db066d Lib/fractions.py
--- a/Lib/fractions.py  Mon Apr 14 22:27:27 2014 -0400
+++ b/Lib/fractions.py  Fri May 30 20:12:07 2014 +0800
@@ -280,7 +280,7 @@

     def __repr__(self):
         """repr(self)"""
-        return ('Fraction(%s, %s)' % (self._numerator, self._denominator))
+        return str(self) + 'r'

     def __str__(self):
         """str(self)"""
diff -r bf65e7db066d Modules/_decimal/_decimal.c
--- a/Modules/_decimal/_decimal.c   Mon Apr 14 22:27:27 2014 -0400
+++ b/Modules/_decimal/_decimal.c   Fri May 30 20:12:07 2014 +0800
@@ -3092,18 +3092,10 @@
 static PyObject *
 dec_repr(PyObject *dec)
 {
-    PyObject *res, *context;
-    char *cp;
-
-    CURRENT_CONTEXT(context);
-    cp = mpd_to_sci(MPD(dec), CtxCaps(context));
-    if (cp == NULL) {
-        PyErr_NoMemory();
-        return NULL;
-    }
-
-    res = PyUnicode_FromFormat("Decimal('%s')", cp);
-    mpd_free(cp);
+    PyObject *res, *str;
+    str = dec_str(dec);
+    res = PyUnicode_FromFormat("%Ud", str);
+    Py_DECREF(str);
     return res;
 }

下面是最终成果啦:

>>> 0.1 + 0.2 == 0.3
False
>>> 0.1d + 0.2d == 0.3d
True
>>> 1/3r + 1/2r
5/6r
>>> 0.4/1.2r
0.33333333333333337
>>> 0.4r/1.2r
1/3r

可以看到,与复数类似,分数字面量其实包含了一次除法。所以如果分子写浮点数的话,最终结果是会被转成浮点数的呢。这个和 Ruby 的行为是一样的 =w=

Category: python | Tags: Python C代码
5
27
2014
13

纯真 IP 数据库 QQWry 解析库 Python 3 版

这东西挺好用的,可惜我只寻到一多年以前的 Python 2 版本的,作者是 AutumnCat,不认识。但注释里提到的修改者 bones7456 是鼎鼎大名的骨头兄,现其博客已经长草……

一直以来,我都是通过子进程调用来使用的,因为我写的代码是 Python 3 版,比如这个寻找文本里的 IP 地址并标记的 ipmarkup 脚本。配合 Python 3.2 加入的 functools.lru_cache,效率还不错的样子。但近期有大量 IP 需要查询,才感到每个 IP 都开个子进程的方式实在太慢。遂将其修改为 Python 3 版,并加入了些 Python 后来才流行的 idiom。

脚本还是扔到 winterpy 仓库里了。GPLv2 授权的。

2014年8月2日更新:增加了在线更新的功能,从此不需要 Wine 就能更新数据库啦 :-) 更新方法来自微菜。更新命令如下:

python3 -m QQWry update
Category: python | Tags: python IP地址
5
15
2014
2

使用 udev 规则自动配置 IP 地址

udev 规则其实挺简单的,但第一次配置也颇费了一番工夫。

事情的起因是这样子的。我的手机,还有 Kindle Paperwhite,都能接电脑上提供一 USB 网络设备,可以用来 ssh 啊 rsync 啊什么的。但是呢,每次接好之后还要执行条命令设置 IP 地址,还要用 sudo、输入密码,很是麻烦。

我用来配置 IP 地址的命令是:

ifconfig usb0 192.168.42.1 # 手机
ifconfig usb0 192.168.15.1 # Kindle

查阅 udev(7) man 文档之后,对 udev 规则有了大概了解,知道大约要写成以下形式:

ACTION=="add", SUBSYSTEM=="net", XXX, RUN+="xxx"

需要一个属性来确定添加的设备是目标设备。插入设备,使用udevadm命令来检查设备的各种属性:

udevadm info --attribute-walk /sys/class/net/usb0

本来准备用 MAC 地址的,但后来才发现我这 Android 手机每次的 MAC 地址都不一样。想到 adb 用的序列号,于是我决定用ATTRS{serial}=="BX90345MWH"。然后轮到 Kindle 了。结果一看,竟然没有序列号属性了……但是它的 MAC 地址不会变,所以用 MAC 地址了。

写好规则之后可以先测试一下:

udevadm test /sys/class/net/usb0

配置正确的话会看到一行以run:开头的行里写着自己定义的命令。

没问题就让 udevd 重新加载规则文件:

sudo udevadm control --reload-rules

到这里似乎就该结束了。可事与愿违,测试都没问题了,但 IP 地址就是没出现。查阅各处文档,也没做错什么呀。后来才注意到测试时上边有一行输出:

run: '/usr/lib/systemd/systemd-sysctl --prefix=/proc/sys/net/ipv4/conf/usb0 --prefix=/proc/sys/net/ipv4/neigh/usb0 --prefix=/proc/sys/net/ipv6/conf/usb0 --prefix=/proc/sys/net/ipv6/neigh/usb0'

它使用的是绝对路径!想起 systemd 的命令必须是绝对路径,我尝试改成绝对路径,果然可以了:

ACTION=="add", SUBSYSTEM=="net", ATTRS{serial}=="BX90345MWH", RUN+="/bin/ifconfig %k 192.168.42.1"
ACTION=="add", SUBSYSTEM=="net", ATTR{address}=="ee:49:00:00:00:00" RUN+="/bin/ifconfig %k 192.168.15.1"
Category: Linux | Tags: linux udev
4
25
2014
4

批量给图片加不同位置的水印

再次成功 root Kindle 之后,突发奇想:把自己的 ID 和二维码显示在其上应该会很有趣。生成二维码用 zint 搞定,添加上 ID 啦阴影啦边缘半透明啦用 GIMP 搞定。可是,我想把自带的 20 张屏保图片全部加上这个小图呢?我当然可以用 ImageMagick 批量添加,但是定位呢?对于不同图片,我要把这二维码放到不同的地方呢!于是终于拿 GTK 2 写了这么个很久以前就想写的程序——imagestamp

功能很简单,用法也很简单,make之后直接执行:

./imagestamp -p my_qr_code.png *.png

然后在弹出的窗口里不断地在适当的位置点击即可 :-)

下边是图片之一。由于对 Kindle 的像素密码估算不足所以那个二维码小了一点。

修改之后的 Kindle 屏保图片

Category: Linux | Tags: 图片 gtk kindle
4
24
2014
8

截图并识别二维码

现在到处都是二维码,于是经常看到某网页有个二维码,又懒得拿出手机开扫码程序来扫。于是有了这个方案:ImageMagick 截图,zbar 识别:

import png:- | zbarimg /dev/stdin

二维码这东西的泛滥是在把人的识别能力拉到与计算机齐平么……什么时候,我能够直接对着一网址扫过去,浏览器就能拿到那个网址并打开呀。

Category: Linux | Tags: QR code ImageMagick
4
16
2014
62

Sublime Text 能做的,Vim 都能做到吗?

前两天突发奇想,想知道现在越来越流行的 Sublime Text 除了易于上手外,和 Vim 相比还有哪些独到的特性呢?

于是我在 SegmentFault 上提出了这么一个问题,最终的答案整理如下。括号中是项目数,由很简单的 Vim 命令统计或者目测完成。

Sublime Text 能做的,Vim 在不做任何配置、或仅作极少量的配置也能做到的(16):

ST: 随时保留文件的修改 / 自动保存。
Vim:
  1. 当意外退出时,默认在最后四秒内的最多200字符可能会丢失。这两个数值都是可以配置的。
  2. 设置 'autowriteall',在需要保存文件时会自动保存文件。
ST: 按 HTML 标签选择。
Vim: vit, vat, v2it 等。各种文本对象。
ST: CSS 的属性名称自动完成。
Vim: CSS 属性名称和值的智能补全。
ST: 会话保持。
Vim: :mksession 以及 :source。当然很容易配置成自动化的。
ST: 跨平台。
Vim: 把 ST 支持平台数平方一下。
ST: 变身:Vim 模式。
Vim: 变身:简易模式。
ST: 多文件搜索。
Vim: :vimgrep 命令。还有 grep.vimag.vim
ST: 在跳转框内使用 : 到达指定行。
Vim: 行号+G 或者 :行号(并回车)。
ST: 并列显示多个文件。
Vim: 不仅支持各种布局的多窗口,还支持多标签页。
ST: 界面干净。
Vim: guioptions 想去掉什么就去掉什么。
ST: 简易的编辑快捷键,如删除单词删除行头行尾。
Vim: daw, d^, d$,还想要什么?。
ST: 早期版本底部多出一个版面的空间,可以把代码最后的几千显示在窗口上半部分。
Vim: z. 把当前行放到窗口中间。Ctrl-E / Ctrl-Y 来滚动显示的行。
ST & Vim: 已拼单词提示/关键字补全。自动缩进。快捷键。命令行。

Sublime Text 能做的,Vim 在安装额外的插件或者适当配置后也能做到的(8):

ST: 快捷键切换注释。
Vim: 多个插件都可以,如 EnhCommentify
ST: 按照文件名称首字母搜索。
Vim: 多个插件都可以,如 LustyExplorer(按文件名子序列搜索)、Command-T(在当前目录下递归地搜索)。
ST: HTML片断快速插入。
Vim: neosnippet、snipMate 等。
ST: Livereload 插件,网页修改后可直接查看效果。
Vim: 使用 BufWritePost 事件通知浏览器刷新。或者直接使用 inotify 等技术监听文件更新。
ST: 侧栏管理项目目录。
Vim: 自带 netrw 插件、更强大的 NERD tree
ST: 多行移动。
Vim: 进可视模式d了再到需要的位置p就可以,不过还是有些麻烦。可以把以下配置复制到你的 vimrc 中:
" 上下移动一行文字
nmap <C-j> mz:m+<cr>`z
nmap <C-k> mz:m-2<cr>`z
vmap <C-j> :m'>+<cr>`<my`>mzgv`yo`z
vmap <C-k> :m'<-2<cr>`>my`<mzgv`yo`z
ST: 括号自动匹配。
Vim: 有几个插件。我不推荐使用这种功能。
ST & Vim: Zencoding。

Sublime Text 能做的,但是 Vim 还不能完全做到的(5):

ST: 多重光标选择,同时修改。
Vim: 列模式。正则替换。. 重复单个命令。使用宏来重复连续的多条命令。
ST: 简单全面的插件体系。
Vim: vim-addon-manager、Vundle、Bundle 等。
ST: UI 清亮。
Vim: 非 Windows 平台上的 GUI 版本也挺不错。Windows 版本的字体渲染很糟糕。
ST: 按名称搜索命令并执行。
Vim: 传统的命令行补全。
ST: 快。
Vim: 少装些插件,避免使用复杂的语法高亮。

Sublime Text 能做的,但是 Vim 还不能做到的(5):

ST: 代码地图 / Minimap。
Vim: 无。不过有好些人表示 taglist / tagbar 之类的比这个好看的缩略图更中用。
ST: 「经常友好地弹一个窗口,提醒用户付费。」
Vim: 默认在启动时会希望你给乌干达的儿童捐款。
ST: 「如果作者不幸挂掉,将会有一大波程序员陷入悲痛。」
Vim: 如果作者不幸挂掉,将会有一大波程序员尝试成为新的维护者。
ST: Linux 下的中文显示可能有问题,不支持输入法。
Vim: 如果其它程序中没有问题,那么在 Vim 中也不会有问题。
ST: 小巧。安装后大小:14277.00 KiB。
Vim: 带 GTK 2 图形界面、多种解释器支持的巨型版本安装后大小:28056.00 KiB。

后记

我当然是不会换用 Sublime Text,除非其出正式版本并对用户的数据表现出一个负责的态度

markdown 不支持描述性列表好讨厌啊,逼我用 Vim 的宏来修正么 :-( 以后这种复杂的内容还是部分用 HTML 吧,简单的部分用 markdown 写了再通过 Vim 的「filter」功能送给 markdown 好了。

本页用到了仅火狐 21+ 支持的 HTML 5 特性:Scoped CSS

Category: Vim | Tags: vim Sublime Text
4
14
2014
6

Linux 3.14: 终于能方便地看到真正的系统可用内存了

直接取/proc/meminfo中的「MemAvailable」项即可:

awk '$1 == "MemAvailable:" { print $2 * 1024 }' /proc/meminfo | filesize

filesize 是我自己写的将字节数转成人可读形式的脚本。

使用free命令的版本:

free | awk 'NR == 3 { print $4 * 1024 }' | filesize

并不准确,因为已缓存(Cached)内存并不一定是可以释放的,比如我用的 tmpfs 里的数据也算进去了。详见内核的这个提交。「free命令的算法在十年前还不错」,这不就是我大学课程教授的知识所处的时代么? :-D

Category: Linux | Tags: Linux
3
31
2014
6

微信内置浏览器调用微信 OAuth 授权获取用户基本信息的方法

首先,我建议各位打印一份微信官方的「网页授权获取用户基本信息」文档,但是不要阅读它。烧掉它,这有重要的象征意义。

一个位于微信内置浏览器内的网页要获取用户基本信息,首先要获取任意一个用户对其应用的所谓「openid」(其实应该叫「private id」,因为同一个用户在每个应用里的这个 ID 都不一样)。这个可以通过只能获取到「openid」的snsapi_base授权得到。然后,将用户重定向于以下地址:

url_fmt = '''
https://open.weixin.qq.com/connect/oauth2/authorize?
appid={appid}
&redirect_uri={redirect_uri}?userid={userid}
&response_type={response_type}&scope={scope}&state={state}
'''.replace('\n', '')

scope当然是snsapi_userinfo,其它参数自己填。redirect_uri最好通过urllib.parse.quote编码一下,虽然好像不编码也可以用。利用url_fmt.format_map或者url_fmt.format方法把参数填进去,重定向之后,微信就会弹出授权提示页面了。

记住,一定要用我这里给出的格式,不要按标准 URL 参数处理方法使用urllib.parse.urlencode方法构造参数部分,因为这个微信授权 URL 里的参数顺序是重要的。具体规则不清楚,大概是appid一定要在redirect_uri前边,scoperesponse_type一定要在其后边。反正你按我这里给出的格式填就对了。

很奇怪为什么不能使用标准 URL 参数处理函数么?那我还告诉你,给微信接口传 JSON 时,标准 JSON Unicode 转义\uXXXX是无效的。

Category: 网络 | Tags: 腾讯 微信

| Theme: Aeros 2.0 by TheBuckmaker.com