用 Python 写有趣的脚本(上) - 掘金

用Python写有趣的脚本(下) 00. 前言 这个项目是我在半年前开始的,当时的计划是每周抽一点工作外的空余时间来写一个python程序(工作不用Python),结果持续不到一个月就弃坑了...于是把他们放上来,用 | 掘金是中国质量最高的技术分享社区,邀请稀土用户作为 Co-Editor 来分享优质的技术干货,从前端到后端开发,从设计到产品,让每一个掘金用户都能挖掘到有价值的干货。

用 Python 写有趣的脚本(上)

阅读 3352
收藏 192
2017-01-08
用 Python 写有趣的脚本。 —— 由稀土君分享

用Python写有趣的脚本(下)

00. 前言

这个项目是我在半年前开始的,当时的计划是每周抽一点工作外的空余时间来写一个python程序(工作不用Python),结果持续不到一个月就弃坑了...于是把他们放上来,用作记录和学习交流吧。

01. 目录文件分类

前言

有时候,想要对一个目录里的文件进行搜索或者分类操作往往是一件痛苦的事情,下面这个程序的目的是将目录下的文件树以某种分类规则进行排列。

用法

usage: classify.py [-h] [-t {ext,mtime,back}] directory

对目录进行文件整理归类.

positional arguments:
  directory             目标目录路径

optional arguments:
  -h, --help            show this help message and exit
  -t {ext,mtime,back}, --type {ext,mtime,back}
                        分类方式

按扩展名分类

python classify.py -t ext 目录路径

效果:

├── DS_Store
├── bat
├── bin
├── css
├── db
├── default
├── gif
├── gitattributes
├── gitignore
├── htaccess
├── jar
├── js
├── json
├── lib
├── log
├── md
├── php
├── plex
├── png
├── sql
├── sublime-project
├── sublime-workspace
├── tpl
├── txt
├── xml
├── y
└── yml

按修改时间分类

python classify.py -t mtime 目录路径

效果:

├── 2014
│   └── 10
│       └── 24
├── 2015
│   └── 12
│       └── 21
└── 2016
    ├── 6
    │   ├── 28
    │   ├── 29
    │   └── 30
    ├── 7
    │   ├── 1
    │   └── 26
    └── 8
        ├── 6
        └── 7

按首字母/数字分类

python classify.py -t word 目录路径

效果:

├── 3
├── 4
├── 5
├── N
├── R
├── W
├── a
├── b
├── c
├── d
├── e
├── f
├── h
├── i
├── j
├── l
├── m
├── o
├── p
├── s
├── t
├── u
├── v
└── y

还原目录

python classify.py -t back 目录路径

效果:

├── assets
│   ├── 2e015166
│   ├── 4893405d
│   │   ├── detailview
│   │   ├── gridview
│   │   └── listview
│   ├── 4a5213fe
│   └── a2744ecd
│       ├── autocomplete
│       ├── jui
│       │   ├── css
│       │   │   └── base
│       │   │       └── images
│       │   └── js
│       ├── rating
│       ├── treeview
│       │   └── images
│       └── yiitab
├── css
├── protected
│   ├── commands
│   ├── components
│   ├── config
│   ├── controllers
│   ├── data
│   ├── extensions
│   │   └── smarty
│   │       ├── demo
│   │       │   ├── plugins
│   │       │   └── templates
│   │       ├── lexer
│   │       └── libs
│   │           ├── plugins
│   │           └── sysplugins
│   ├── filters
│   ├── messages
│   │   └── zh_cn
│   ├── models
│   ├── runtime
│   ├── sql_source
│   ├── tests
│   │   ├── functional
│   │   └── unit
│   └── views
│       ├── blog
│       ├── layouts
│       ├── login
│       ├── postadmin
│       └── useradmin
└── themes
    └── classic
        └── views

源码

#!/usr/bin/python
#-*- coding:utf-8 -*-
import argparse
import os
import json
import shutil
import sys
import time
from uuid import uuid4

DEFAULT_KEY = 'default'
BACKUP_FILE = '.backup.json'

# 防止迁移的时候出现文件重名的情况
def unique_covert(file_path):
    new_path = '|'.join(file_path.split(os.sep)[:-1])
    if not new_path:
        return file_path
    return os.path.basename(file_path) + ' (' + new_path + ')'

def coroutine(gen):
    def wrapper(*arg, **kws):
        coroutine = gen(*arg, **kws)
        next(coroutine)
        return coroutine
    return wrapper

@coroutine
def save_back_up(target_dir):
    string_len   = len(target_dir)
    back_up_file = os.path.join(target_dir, BACKUP_FILE)
    if os.path.exists(back_up_file):
        go_back(target_dir)
    back_up_tree = {}
    while True:
        tup = yield
        if not tup:
            break
        (now, prev) = tup
        back_up_tree[now] = prev
    with open(back_up_file, 'w') as buf:
        json.dump(back_up_tree, buf, indent=4)        

# 按扩展名分类
def classify_by_ext(target_dir, tmp_dir):
    from collections import defaultdict
    ext_files = defaultdict(list)
    for dir_ in os.walk(target_dir):
        for f in dir_[2]:
            exts = f.split('.')
            key  = exts[-1] if len(exts) != 1 else DEFAULT_KEY
            ext_files[key].append(os.path.join(dir_[0], f))
    for ext, file_list in ext_files.items():
        dest_dir = os.path.join(tmp_dir, ext)
        if not os.path.exists(dest_dir):
            os.makedirs(dest_dir)
        for file_path in file_list:
            filename      = unique_covert(os.path.relpath(file_path, target_dir))
            dest          = os.path.join(dest_dir, filename)
            rel_file_path = os.path.relpath(file_path, target_dir)
            shutil.move(file_path, dest)
            yield (os.path.join(ext, filename), rel_file_path)
    yield None

# 按修改时间分类
def classify_by_mtime(target_dir, tmp_dir):
    for dir_ in os.walk(target_dir):
        base_dir = dir_[0]
        for f in dir_[2]:
            abs_file_path = os.path.join(base_dir, f)
            rel_file_path = os.path.relpath(abs_file_path, target_dir)
            if os.path.islink(abs_file_path):
                rel_dest_dir = 'link'
                dest_dir     = os.path.join(tmp_dir, 'link')
            else:
                mtime        = os.stat(abs_file_path)[8]
                (y, m, d)    = map(str, time.localtime(mtime)[:3])
                rel_dest_dir = os.path.join(y, m, d)
                dest_dir     = os.path.join(tmp_dir, rel_dest_dir)
            if not os.path.exists(dest_dir):
                os.makedirs(dest_dir)
            filename  = unique_covert(rel_file_path)
            dest_file = os.path.join(dest_dir, filename)
            shutil.move(abs_file_path, dest_file)
            yield (os.path.join(rel_dest_dir, filename), rel_file_path)
    yield None

# 按字母分类
def classify_by_first_letter(target_dir, tmp_dir):
    for dir_ in os.walk(target_dir):
        base_dir = dir_[0]
        for f in dir_[2]:
            abs_file_path = os.path.join(base_dir, f)
            rel_file_path = os.path.relpath(abs_file_path, target_dir)
            first_char = f[0]
            if first_char.isalnum():
                dest_dir = os.path.join(tmp_dir, first_char)
                if not os.path.exists(dest_dir):
                    os.makedirs(dest_dir)
                filename  = unique_covert(rel_file_path)
                dest_file = os.path.join(dest_dir, filename)
                shutil.move(abs_file_path, dest_file)
                yield (os.path.join(first_char, filename), rel_file_path)
            else:
                shutil.move(abs_file_path, os.path.join(tmp_dir, f))
                yield (f, rel_file_path)
    yield None


def go_back(target_dir):
    target_dir = target_dir.decode('utf-8')
    tmp_dir    = os.path.join(os.path.dirname(os.path.dirname(target_dir)), str(uuid4()))
    os.mkdir(tmp_dir)
    back_up_tree = {}
    back_up_file = os.path.join(target_dir, BACKUP_FILE)
    if not os.path.exists(back_up_file):
        raise Exception('已经是初始状态')
    with open(back_up_file, 'rb') as buf:
        back_up_tree = json.load(buf)
    if not back_up_tree:
        raise Exception('备份文件已损坏或不存在')
    for src, old in back_up_tree.items():
        src_file  = os.path.join(target_dir, src)
        dest_file = os.path.join(tmp_dir, old)
        dest_dir  = os.path.dirname(dest_file)
        if not os.path.exists(dest_dir):
            os.makedirs(dest_dir)
        shutil.move(src_file, dest_file)

    shutil.rmtree(target_dir, ignore_errors=False)
    os.rename(tmp_dir, target_dir)

def run(target_dir, classify_func):
    tmp_dir = os.path.join(os.path.dirname(os.path.dirname(target_dir)), str(uuid4()))
    os.mkdir(tmp_dir)

    save_backup_gen = save_back_up(target_dir)
    classify_gen    = classify_func(target_dir, tmp_dir)
    finished        = 0
    begin           = time.time()
    while True:
        tup      = classify_gen.send(None)
        finished += 1
        sys.stdout.write(u'已完成%s个文件\r' % finished)
        sys.stdout.flush()
        if not tup:
            break
        save_backup_gen.send(tup)
    print(u'已完成%s个文件,耗时%s秒' % (finished, time.time() - begin))

    shutil.rmtree(target_dir, ignore_errors=False)
    os.rename(tmp_dir, target_dir)
    try:
        save_backup_gen.send(None)
    except StopIteration:
        pass

def _main():
    parser = argparse.ArgumentParser(description='对目录进行文件整理归类.')
    parser.add_argument('directory', type=str, help='目标目录路径')
    parser.add_argument('-t', '--type', type=str, default='ext', choices=['ext', 'mtime', 'word', 'back'], help='分类方式')

    args       = parser.parse_args()
    target_dir = args.directory
    op         = args.type

    if op == 'ext':
        run(target_dir, classify_by_ext)
    elif op == 'mtime':
        run(target_dir, classify_by_mtime)
    elif op == 'word':
        run(target_dir, classify_by_first_letter)
    elif op == 'back':
        go_back(target_dir)
        print ('恢复完成')
    else:
        raise Exception('参数错误')

if __name__ == '__main__':
    _main()

02. 12306余票查询工具

前言

利用12306提供的相关接口对12306的余票信息进行查询。

用法

usage: left_ticket.py [-h] [-f FROM_CITY] [-t TO_CITY] [-d DATE] [-s] [-l]

查询12306车次余票.

optional arguments:
  -h, --help            show this help message and exit
  -f FROM_CITY, --from_city FROM_CITY
                        起始城市
  -t TO_CITY, --to_city TO_CITY
                        目标城市
  -d DATE, --date DATE  日期,格式如:2016-08-14
  -s, --student         学生票
  -l, --list_city       查看支持城市列表

查询车票

python left_ticket.py -f 唐家湾 -t 广州南 -d 8-26 # 年份默认为今年

输出

车次序号     起始站 出发站 终点站 时间 一等座 二等座
6e000C761003 珠海->唐家湾->广州南 09:48  54   257
6e000C762202 珠海->唐家湾->广州南 11:43  39   235
6e000C763002 珠海->唐家湾->广州南 13:40  43   307
6e000C763802 珠海->唐家湾->广州南 14:45  58   329
6e000C764602 珠海->唐家湾->广州南 16:43  57   312
6e000C765802 珠海->唐家湾->广州南 18:38  62   356
6e000C766202 珠海->唐家湾->广州南 19:45  55   357
6e000C767602 珠海->唐家湾->广州南 22:10  64   389
6e000C767802 珠海->唐家湾->广州南 22:33  64   384

向导模式

python left_ticket.py

输出

请输入起始城市(输入回车为珠海):
请输入目的城市:广州南
请输入出发日期(输入回车为2016-08-14):
是否成人票,是请按回车,不是请输入n:
正在查询...

车次序号     起始站 出发站 终点站 时间 一等座 二等座
6e000C768602 珠海->珠海->广州南 23:28  626e000C768802 珠海->珠海->广州南 23:58  112   33

查看支持城市

python left_ticket.py -l

输出

阜南 祁县东 黑水 涪陵 哈密 大灰厂 新余北 平安 钦州东 安陆 黎塘 高各庄 谷城 彭水 沙县 海安县 枣庄西 昆山南 克东 图强 大苴 恩施 水富 沂南 姚千户屯 冷水江东 ...

源码

#!/usr/bin/python
#-*- coding:utf-8 -*-

import argparse
import os
import json
import urllib2
import ssl
import sys
import re
import socket
from datetime import datetime

PURPOSE_CODES   = ['ADULT', '0X00'] # 成人票,学生票
CITY_CACHE      = None
CITY_CACHE_FILE = '.cities'
ADDR_CACHE_FILE = '.addr'
CITY_LIST_URL   = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js'
ACTION_URL      = 'https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes={ticket_type}&queryDate={train_time}&from_station={from_city}&to_station={to_city}'
SSL_CTX         = ssl.SSLContext(ssl.PROTOCOL_TLSv1)

# 对月份进行补零
def add_zero(month):
    if int(month) < 10:
        month = '0' + str(int(month))
    return month

# 默认为今天
def default_date():
    now = datetime.now()
    return '-'.join([str(now.year), str(add_zero(now.month)), str(add_zero(now.day))])

# 格式化输入日期
# 如:
# 8-14 -> 2016-08-14
# 2016:8:14 -> 2016-08-14
#  -> 2016-08-14
def date_format(input_date):
    if not input_date:
        return default_date()
    res = re.match(r'(([0-9]{4})[-|\\|:])?([0-9]{1,2})[-|\\|:]([0-9]{2})', input_date)
    if res:
        year  = res.group(2)
        month = res.group(3)
        day   = res.group(4)

        now = datetime.now()
        if not year:
            year = now.year
        if not month:
            month = now.month
        if not day:
            day = now.day
        return '-'.join([str(year), add_zero(str(month)), str(day)])
    else:
        print ('输入日期格式错误')
        sys.exit(-1)

# 加载城市信息
def load_cities():
    global CITY_CACHE
    if CITY_CACHE is not None:
        return CITY_CACHE
    cache_file  = os.path.join(os.path.dirname(os.path.abspath(__file__)), CITY_CACHE_FILE)
    need_reload = True
    cities      = {}
    if os.path.exists(cache_file):
        with open(cache_file, 'rb') as fp:
            cities = json.load(fp)
        if cities:
            need_reload = False

    if need_reload is True:
        city_info = urllib2.urlopen(CITY_LIST_URL, context=SSL_CTX).read()
        for res in re.finditer(r'@[a-z]{3}\|(.+?)\|([A-Z]{3})\|[a-z]+?\|[a-z]+?\|', city_info):
            city         = res.group(1)
            code         = res.group(2)
            cities[city] = code
        with open(cache_file, 'w') as fp:
            json.dump(cities, fp)
    CITY_CACHE = cities
    return cities

# 查询操作
def search(from_city, to_city, train_time, ticket_type='ADULT'):
    cities      = load_cities()

    try:
        from_code = cities[from_city.decode('utf-8')]
    except KeyError:
        print('指定起始站点%s不存在' % from_city)
        sys.exit(-1)
    try:
        to_code   = cities[to_city.decode('utf-8')]
    except KeyError:
        print('指定目标站点%s不存在' % to_city)
        sys.exit(-1)

    url = ACTION_URL.format(from_city=from_code, to_city=to_code, train_time=train_time, ticket_type=ticket_type)
    ret = json.loads(urllib2.urlopen(url, context=SSL_CTX, timeout=10).read())
    if not ret or ret == -1 or not ret['data'].has_key('datas') or len(ret['data']['datas']) == 0:
        print('没查询到相关的车次信息')
        sys.exit(-1)

    print ('车次序号     起始站 出发站 终点站 时间 一等座 二等座')
    for r in ret['data']['datas']:
        if (not r['zy_num'].encode('utf-8').isdigit() 
            and not r['ze_num'].encode('utf-8').isdigit()
            or r['from_station_name'].encode('utf-8') != from_city):
            continue
        print (u'%s %s->%s->%s %s  %s   %s' %(
            r['train_no'],
            r['start_station_name'],
            r['from_station_name'],
            r['to_station_name'],
            r['arrive_time'],
            r['zy_num'],
            r['ze_num']
        ))

# 获取ip
def getip():
    url    = 'http://jsonip.com'
    if url == opener.geturl():
        res  = re.search('\d+\.\d+\.\d+\.\d+', urllib2.urlopen(url, timeout=5).read())
        if res:
            return res.group(0)
    return None

# 根据ip获取地址
def getaddr(fresh=False):
    addr_cache_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ADDR_CACHE_FILE)
    if not fresh and os.path.exists(addr_cache_file):
        addr = None
        with open(addr_cache_file, 'rb') as fp:
            addr = fp.read()
        if addr:
            return addr
    ip = getip()

    if not ip:
        return None
    addr_info = urllib2.urlopen('http://ip.taobao.com/service/getIpInfo.php?ip=%s' % ip, timeout=5).read()
    city = None
    if addr_info:
        addr_info = json.loads(addr_info)
        city      = addr_info['data']['city']
        city      = city.encode('utf-8').replace('市', '')
        with open(addr_cache_file, 'w') as fp:
            fp.write(city)
    return city

def get_yn_input(msg):
    while True:
        res = raw_input('%s,是请按回车,不是请输入n:' % msg)
        if res in ('', 'n'):
            break
    return True if res == None else False

# 默认模式
def guide():
    try:
        cities = load_cities()
        city   = getaddr()
    except socket.timeout:
        print ('请求超时')
        sys.exit(-1)

    if city and cities.has_key(city.decode('utf-8')):
        from_city = raw_input('请输入起始站点(输入回车为%s):' % city)
        if not from_city:
            from_city = city
    else:
        from_city = raw_input('请输入起始站点:')
    while True:
        to_city = raw_input('请输入目的站点:')
        if to_city:
            break

    dd          = default_date()
    train_time  = raw_input('请输入出发日期(输入回车为%s):' % dd)
    train_time  = date_format(train_time) if train_time else dd
    ticket_type = 'ADULT' if get_yn_input('是否成人票') else '0X00'
    print('正在查询...\n')
    search(from_city, to_city, train_time, ticket_type)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='查询12306车次余票.')
    parser.add_argument('-f', '--from_city', type=str, help='起始城市')
    parser.add_argument('-t', '--to_city', type=str, help='目标城市')
    parser.add_argument('-d', '--date', type=str, help='日期,格式如:2016-08-14')
    parser.add_argument('-s', '--student', action='store_true', help='学生票')
    parser.add_argument('-l', '--list_city', action='store_true', help='查看支持城市列表')

    args        = parser.parse_args()
    from_city   = args.from_city
    to_city     = args.to_city
    train_time  = date_format(args.date)
    ticket_type = PURPOSE_CODES[1] if args.student is True else PURPOSE_CODES[0]
    list_city   = args.list_city

    if from_city is None and to_city is None and list_city is False:
        guide()
    else:
        if list_city:
            for city, code in load_cities().items():
                print city,
        elif from_city and to_city and ticket_type:
            search(from_city, to_city, train_time, ticket_type)
        else:
            print ('参数错误')

03. 文本备份云仓库

前言

everbox是一个将evernote作为文件沙盒的接口集合,利用evernote作为文本的存储仓库,方便地对文本文件进行管理。

用法

usage: everbox.py [-h] {init,push,pushall,list,drop,drag,remove,pull,log} ...

文本备份助手.

optional arguments:
  -h, --help            show this help message and exit

操作命令:
  {init,push,pushall,list,drop,drag,remove,pull,log}
    init                新建一个仓库
    push                添加文本到仓库
    pushall             添加批量文本到仓库
    list                列出仓库或文本
    drop                删除一个仓库
    drag                从远程拉取一个文件同时删除记录
    remove              从仓库删除指定id的文本
    pull                从仓库拉取文本
    log                 查看文本记录信息

准备工作

安装evernote sdk for python

pip install evernote

登录Evernote开发者,获取开发Token,把获取到的token替换掉代码中的dev_token。

基本操作

init 新建一个仓库

usage: everbox.py init [-h] box

新建一个仓库

positional arguments:
  box         仓库名字
python everbox.py init test
创建成功,id为:0c6e25c4-538c-4008-87e2-7efe32e18280

list 列出仓库或文本

usage: everbox.py list [-h] [box]

列出仓库文本

positional arguments:
  box         仓库id或仓库名字

获取所有仓库

python everbox.py list
| 文本id          | 仓库名称 |
6da27e72-ad2d-4cd0-a05a-f1fc12d9e44c 我的第一个笔记本
1902a691-62f3-4edc-a8bb-4db6d949da50 示例笔记本

获取仓库文本

python everbox.py list 6da2
| 文本id          | 文本名称 |
b00204f8-41d0-43bb-8fc3-17b3a654360f README.md
f7c7b2be-c247-4c2a-8001-186d27942cce README.md

pushall 推送所有文本

usage: everbox.py pushall [-h] [-b BOX] [files [files ...]]

添加批量文本到仓库

positional arguments:
  files              文本路径,多个以空格间隔

optional arguments:
  -h, --help         show this help message and exit
  -b BOX, --box BOX  仓库id或仓库名字
python everbox.py pushall -b 6da2 README.md
已上传(1/1)个文本
python everbox.py pushall README.md
无指定仓库,将使用默认仓库
已上传(1/1)个文本

log 查看文件在仓库中的记录

usage: everbox.py log [-h] file

查看文本记录信息

positional arguments:
  file        文本名称
python everbox.py log README.md

输出

| 文本id          | 文本名称 | 仓库  | 创建时间
b00204f8-41d0-43bb-8fc3-17b3a654360f README.md 我的第一个笔记本 2016-08-16 17:14:07
f7c7b2be-c247-4c2a-8001-186d27942cce README.md 我的第一个笔记本 2016-08-16 17:15:02

pull 从仓库中拉取文件

usage: everbox.py pull [-h] [-b BOX] [-y] [files [files ...]] directory

从仓库拉取文本

positional arguments:
  files              文本guid或名称(若用名称则取最新的同名
                     ),多个以空格间隔
  directory          拉取目录

optional arguments:
  -h, --help         show this help message and exit
  -b BOX, --box BOX  仓库id或仓库名字
  -y, --yes          忽略覆盖提示
python everbox.py pull b00204f8-41d0-43bb-8fc3-17b3a654360f  .

输出

文件 /Users/tonnie/github/one-week/03-everbox/README.md 已存在,是否覆盖,是请按y,不是请输入n:y
成功拉取:1个文件

remove 从仓库删除指定的文本

usage: everbox.py remove [-h] guid

从仓库删除指定id的文本

positional arguments:
  guid        文本guid
python everbox.py remove d8bc4812-bfc2-44cd-9aee-bc7a92887e70

输出

删除成功

drag 从远程拉取一个文件同时删除记录

usage: everbox.py drag [-h] guid directory

从远程拉取一个文本同时删除记录

positional arguments:
  guid        文本guid
  directory   拉取目录
python everbox.py drag f7c7b2be-c247-4c2a-8001-186d27942cce ~

输出

拉取完成
删除成功

drop 删除一个仓库

usage: everbox.py drop [-h] box

删除一个仓库

positional arguments:
  box         仓库id或仓库名字
python everbox.py drop 我的第一个笔记本

输出

删除成功

源码

#!usr/bin/python
#-*- coding:utf-8 -*-
import argparse
import binascii
import hashlib
import os
import sys
import base64
from datetime import datetime, date

try:
    import evernote
    from evernote.api.client import EvernoteClient
    import evernote.edam.type.ttypes as Types
    import evernote.edam.notestore.ttypes as NoteTypes
except:
    print ('未安装evernote的扩展,请安装后重试')
    sys.exit(-1)

try:
    import socket
    s = socket.create_connection(('sandbox.evernote.com', 80), 5)
except Exception,e:
    print('无法连接到evernote,请检查网络连接')
    sys.exit(-1)

dev_token  = "S=s1:U=92d14:E=15de8ebccac:C=156913a9da8:P=1cd:A=en-devtoken:V=2:H=ca0bbceb23208c3cde8227aa5912761a"

from contextlib import contextmanager

_loading = True

def loading(hint):
    while _loading:
        sys.stdout.write(hint + '\r')
        sys.stdout.flush()

# 耗时操作的提示
@contextmanager
def open_loading(hint):
    from threading import Thread
    global _loading
    _loading = True
    t = Thread(target=loading, args=[hint])
    t.start()
    yield
    _loading = False

class LazyGet:
    def __init__(self, construct_func, hint=None, end_hint=None, excp_hint=None):
        self._func      = construct_func
        self._ins       = None
        self._hint      = hint
        self._end_hint  = end_hint
        self._excp_hint = excp_hint

    def __call__(self):
        if self._ins is not None:
            return self._ins
        print (self._hint)
        try:
            self._ins = self._func()
        except Exception, e:
            print (self._excp_hint)
            sys.exit(-1)
        print (self._end_hint)
        return self._ins

get_client    = LazyGet(lambda : EvernoteClient(token=dev_token), '初始化组件...', '初始化成功', '初始化失败')
get_box_store = LazyGet(lambda : get_client().get_note_store(), '连接evernote...', '连接成功', '连接失败')
get_boxes     = LazyGet(lambda : get_box_store().listNotebooks(), '获取仓库信息...', '获取仓库成功', '获取失败')

def create_box(name):
    box      = Types.Notebook()
    box.name = name
    return box

def create_file(title, content, box_id='', image_resource=None):
    hash_hex = ''
    file     = Types.Note()
    if box_id:
        file.notebookGuid = box_id

    if image_resource:
        (resource, image_hash) = image_resource
        file.resources         = [resource]
        hash_hex               = binascii.hexlify(image_hash)

    file.title   = title
    file.content = '''<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
    <en-note><caption>%s</caption>''' % content
    if hash_hex:
        file.content += '<en-media type="image/png" hash="%s"/>' % hash_hex
    file.content += '</en-note>'

    return file

def parse_file(content):
    file_content = ''
    try:
        content = content.split('<caption>')[1].split('</caption>')[0]
    except:
        pass

    media_index = content.find('<en-media')
    content     = content[:media_index]

    file_lst = content.split('<br/>')
    for content in file_lst:
        file_content += content

    return file_content

def load_file(guid):
    file = None
    try:
        file = get_box_store().getNote(dev_token, guid, True, True, False, False)
    except Exception, e:
        return None

    file_content = parse_file(file.content)
    return {
        'title': file.title,
        'content': base64.b64decode(file_content + '=' * (-len(file_content) % 4)),
        'created': date.fromtimestamp(file.created / 100).strftime('%d/%m/%Y')
    }

def get_box_by_id(box_id):
    if len(box_id) < 4:
        print ('id匹配长度至少为4')
        sys.exit(-1)
    for box in get_boxes():
        if box.guid.startswith(box_id):
            return box
    return ''

def get_box_by_name(name):
    for box in get_boxes():
        if box.name == name:
            return box
    return ''

def get_box(id_or_name):
    if not id_or_name:
        return ''
    box = get_box_by_id(args.box)
    if not box:
        box = get_box_by_name(args.box)
    if not box:
        print ('指定仓库不存在')
        sys.exit(-1)
    return box

def push_to_box(note):
    note = get_box_store().createNote(dev_token, note)
    return note

def get_yn_input(msg):
    while True:
        res = raw_input('%s,是请按y,不是请输入n:' % msg)
        if res in ('y', 'n'):
            break
    return True if res == 'y' else False

def get_abs_dir(directory):
    dest_dir = directory or os.path.abspath('.')
    if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
        print ('指定目录不存在')
        sys.exit(-1)
    dest_dir = os.path.abspath(os.path.normpath(os.path.expanduser(dest_dir)))
    return dest_dir

def datetime_format(time):
    return datetime.fromtimestamp(int(str(time)[:-3])).strftime('%Y-%m-%d %H:%M:%S')

def init(args):
    boxname = args.box
    box     = create_box(boxname)        
    try:
        box = get_box_store().createNotebook(dev_token, box)
        if not box:
            raise
        print ('创建成功,id为:%s' % box.guid)
    except Exception,e:
        print ('创建失败')

def pull(args):
    dest_dir   = get_abs_dir(args.directory)
    skip_cover = args.yes
    finished   = 0
    for f in args.files:
        try:
            flag = os.path.basename(f)
            file = load_file(flag)
            if not file:
                box         = get_box(args.box)
                file_filter = NoteTypes.NoteFilter()
                if box:
                    file_filter.notebookGuid = box.guid
                file_filter.title = flag
                files             = get_box_store().findNotes(dev_token, file_filter, 0, 100).notes
                file              = load_file(files[-1].guid)
            if not file:
                raise
            ouput_file = os.path.join(dest_dir, file['title'])
            if os.path.exists(ouput_file):
                if not skip_cover:
                    if not get_yn_input('文件 %s 已存在,是否覆盖' % ouput_file):
                        continue
            with open(ouput_file, 'w') as fp:
                fp.write(file['content'])
            finished += 1
            sys.stdout.write('已拉取:%s个文件\r' % finished)
            sys.stdout.flush()
        except Exception, e:
            print ('文件:%s拉取失败,跳过..' % f)
    print('成功拉取:%s个文件' % finished)

def list(args):
    def list_box(box):
        file_filter              = NoteTypes.NoteFilter()
        file_filter.notebookGuid = box.guid
        files                    = get_box_store().findNotes(dev_token, file_filter, 0, 100).notes

        if not files:
            print '仓库没有任何文本'
        else:
            print ('| 文本id                           | 文本名称 | 创建时间')
            for f in reversed(files):
                print "%s %s %s" %(f.guid, f.title, datetime_format(f.created))

    # 没有指定仓库,则列出所有仓库
    if args.box is None:
        boxes = get_boxes()
        print ('| 仓库id                           | 仓库名称 | 创建时间')
        for box in boxes:
            print box.guid, box.name, datetime_format(box.serviceCreated)
    else:
        box = get_box(args.box)
        list_box(box)

def push(args):
    box      = get_box(args.box)
    abs_path = os.path.abspath(os.path.normpath(args.file))
    if not os.path.exists(abs_path):
        print ('文本路径不存在')
        return
    try:
        with open(abs_path, 'rb') as fp:
            title = os.path.basename(abs_path)
            if args.box:
                box  = get_box(args.box)
                file = create_file(title, fp.read(), box.guid, '')
            else:
                print ('无指定仓库,将使用默认仓库')
                file = create_file(title, base64.b64encode(fp.read()), None, '')
            push_to_box(file)
            print ('%s 上传成功' % abs_path)
    except Exception, e:
        print e
        print ('上传%s时,发生异常' % abs_path)

def pushall(args):
    files  = args.files
    to_add = []
    for f in files:
        abs_path = os.path.abspath(os.path.normpath(f))
        if not os.path.exists(abs_path):
            print ('%s:文本路径不存在,跳过' % f)
            continue
        if os.path.getsize(abs_path) > 1000 * 1000 * 10: # 10M
            print ('%s: 文本体积大于10M,跳过' % f)
            continue
        to_add.append(os.path.abspath(os.path.normpath(f)))
    else:
        total    = len(to_add)
        finished = 0
        for f in to_add:
            try:
                with open(f, 'rb') as fp:
                    title = os.path.basename(f)
                    if args.box:
                        box  = get_box(args.box)
                        file = create_file(title, fp.read(), box.guid, '')
                    else:
                        print ('无指定仓库,将使用默认仓库')
                        file = create_file(title, fp.read(), None, '')
                    push_to_box(file)
                    finished += 1
                    sys.stdout.write('已上传(%s/%s)个文本\r' % (finished, total))
                    sys.stdout.flush()
            except Exception, e:
                print ('上传%s时,发生异常' % f)
        print('已上传(%s/%s)个文本' % (finished, total))

def remove(args):
    guid = args.guid
    try:
        get_box_store().deleteNote(dev_token, guid)
        print ('删除成功')
    except:
        print ('删除失败')

def drop(args):
    box = get_box(args.box)
    try:
        get_box_store().expungeNotebook(dev_token, box.guid)
        print ('删除成功')
    except:
        print ('删除失败')

def drag(args):
    dest_dir = get_abs_dir(args.directory)
    file     = load_file(args.guid)
    if not file:
        print ('文件拉取失败')
    output_file = os.path.join(os.path.join(dest_dir, file['title']));
    if os.path.exists(output_file):
        if not get_yn_input('文件 %s 已存在,是否覆盖' % output_file):
            return
    with open(output_file, 'w') as fp:
        fp.write(file['content'])
    print ('拉取完成')
    remove(args)

def log(args):
    file     = args.file
    abs_path = os.path.abspath(os.path.normpath(file))
    if not os.path.exists(abs_path):
        print ('文件不存在!')
        return
    file_name         = os.path.basename(abs_path)
    file_filter       = NoteTypes.NoteFilter()
    file_filter.title = file_name
    files             = get_box_store().findNotes(dev_token, file_filter, 0, 100).notes
    if not files:
        print ('仓库中不存在该文件的记录')
        return
    print ('| 文本id                           | 文本名称 | 仓库\t | 创建时间')
    for f in files:
        print '%s %s %s %s' %(f.guid, f.title, get_box_by_id(f.notebookGuid).name, datetime_format(f.created))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='文本备份助手.')
    subparsers = parser.add_subparsers(title='操作命令')

    init_cmd  = subparsers.add_parser('init', help='新建一个仓库', description='新建一个仓库')
    init_cmd.add_argument('box', help='仓库名字')
    init_cmd.set_defaults(func=init)

    push_cmd  = subparsers.add_parser('push', help='添加文本到仓库', description='添加文本到仓库')
    push_cmd.add_argument('-b', '--box', help='仓库id或仓库名字')
    push_cmd.add_argument('file')
    push_cmd.set_defaults(func=push)

    push_all_cmd = subparsers.add_parser('pushall', help='添加批量文本到仓库', description='添加批量文本到仓库')
    push_all_cmd.add_argument('-b', '--box', help='仓库id或仓库名字')
    push_all_cmd.add_argument('files', nargs='*', help='文本路径,多个以空格间隔')
    push_all_cmd.set_defaults(func=pushall)

    list_cmd = subparsers.add_parser('list', help='列出仓库或文本', description='列出仓库或文本')
    list_cmd.add_argument('box', nargs='?', help='仓库id或仓库名字')
    list_cmd.set_defaults(func=list)

    drop_cmd = subparsers.add_parser('drop', help='删除一个仓库', description='删除一个仓库')
    drop_cmd.add_argument('box', help='仓库id或仓库名字')
    drop_cmd.set_defaults(func=drop)

    drag_cmd = subparsers.add_parser('drag', help='从远程拉取一个文件同时删除记录', description='从远程拉取一个文件同时删除记录')
    drag_cmd.add_argument('guid', help='文本guid')
    drag_cmd.add_argument('directory', type=str, help='拉取目录')
    drag_cmd.set_defaults(func=drag)

    remove_cmd = subparsers.add_parser('remove', help='从仓库删除指定id的文本', description='从仓库删除指定id的文本')
    remove_cmd.add_argument('guid', help='文本guid')
    remove_cmd.set_defaults(func=remove)

    pull_cmd = subparsers.add_parser('pull', help='从仓库拉取文本', description='从仓库拉取文本')
    pull_cmd.add_argument('-b', '--box', help='仓库id或仓库名字')
    pull_cmd.add_argument('-y', '--yes', action='store_true', help='忽略覆盖提示')
    pull_cmd.add_argument('files', nargs='*', help='文本guid或名称(若用名称则取最新的同名),多个以空格间隔')
    pull_cmd.add_argument('directory', type=str, help='拉取目录')
    pull_cmd.set_defaults(func=pull)

    log_cmd = subparsers.add_parser('log', help='查看文本记录信息', description='查看文本记录信息')
    log_cmd.add_argument('file', help='文本名称')
    log_cmd.set_defaults(func=log)

    args = parser.parse_args()
    args.func(args)

04. 12306余票查询工具

前言

把图像转为ascii字符。

用法

python ascii-image.py [图像路径]

输出


Paste_Image.png

源码

#-*- coding:utf-8 -*-
from PIL import Image
import sys
import os

# 加上颜色
class BColor:
    HEADER    = '\033[95m'
    OKBLUE    = '\033[94m'
    OKGREEN   = '\033[92m'
    WARNING   = '\033[93m'
    FAIL      = '\033[91m'
    ENDC      = '\033[0m'
    BOLD      = '\033[1m'
    UNDERLINE = '\033[4m'

def _main():
  try:
    pic      = os.path.abspath(sys.argv[1])
  except:
    print('指定图片路径')
  img      = Image.open(pic)
  width    = int(img.size[0])
  height   = int(img.size[1])
  gray_img = img.convert('L')
  scale    = width // 100
  char_lst = ' .:-=+*#%@'
  char_len = len(char_lst)

  arr = []
  for y in range(0, height, scale):
    for x in range(0, width, scale):
      brightness = 0
      r = g = b = 0
      count = 0
      for ix in range(scale):
        for iy in range(scale):
          if (x + ix) == width or (y + iy) == height:
            break
          count += 1
          b = 255 - gray_img.getpixel((x+ix, y+iy))
          brightness += b
      choice = int(char_len * (brightness // count / 255) ** 1.2)
      if choice >= char_len:
        choice = char_len
      sys.stdout.write(char_lst[choice])
    sys.stdout.write('\n')
    sys.stdout.flush()

if __name__ == '__main__':
  _main()

05. html生成器

前言

用python生成html

用法

from htmlgen import h

h('html') # <html></html>

更深入的例子

datas = [
    ['1 + 1', 2],
    ['1 + 2', 3],
    ['2 + 2', 4]
]

参数语法

build(h('html', c=[
        h('head', c=[
            h('title', 'My Title'),
            h('meta', charset='utf-8'),
            hc('This is comment'),
            hcss('test.css')
        ]),
        h('body#main.class1 class2', c=[
            h('h1', 'HtmlGen') * 2,
            h('ul', extra=1, c=[
                hmap('li', 'I am {?}!', [1,2,3,4,5])
            ]),
            h('ul', c=[
                hfor(5, lambda i: h('li', '{}').format(chr(ord(str(i)) + 17)))
            ]),
            h('table', c=[
                h('tr', c=[
                    h('td'),
                    heach(dict(name='result'), lambda k, v: h('td', v))
                ]),
                heach(datas, lambda v:
                    h('tr', c=[
                        heach(v, lambda v: h('td', v, style='border:1px solid black;')),
                    ])
                )
            ]),
            ~ hjs('test.js')
        ]),
    ]), 'test.html')

注入语法

build(h('html') <= [
        h('head') <= [
            h('title', 'My Title'),
            h('meta', charset='utf-8'),
            hc('This is comment'),
            hcss('test.css')
        ],
        h('body#main.class1 class2') <= [
            h('h1', 'HtmlGen') * 2,
            h('ul', extra=1) <= [
                hmap('li', 'I am {?}!', [1,2,3,4,5])
            ],
            h('ul') <= [
                hfor(5, lambda i: h('li', '{}').format(chr(ord(str(i)) + 17)))
            ],
            h('table') <= [
                h('tr') <= [
                    h('td'),
                    heach({'name': 'result'}, lambda k, v: h('td', v))
                ],
                heach(datas, lambda v:
                    h('tr') <= [
                        heach(v, lambda v: h('td', v, style='border:1px solid black;')),
                    ]
                )
            ],
            ~ hjs('test.js') # comment,too
        ],
    ], 'test.html')

以上代码会生成如下html:

<html>

<head>
    <title>My Title</title>
    <meta charset="utf-8"></meta>
    <!-- This is comment -->
    <link rel="stylesheet" type="text/css" href="test.css">
</head>

<body id="main" class="class1 class2">
    <h1>HtmlGen</h1>
    <h1>HtmlGen</h1>
    <ul extra="1">
        <li>I am 1!</li>
        <li>I am 2!</li>
        <li>I am 3!</li>
        <li>I am 4!</li>
        <li>I am 5!</li>
    </ul>
    <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
        <li>D</li>
        <li>E</li>
    </ul>
    <table>
        <tr>
            <td></td>
            <td>result</td>
        </tr>
        <tr>
            <td style="border:1px solid black;">1 + 1</td>
            <td style="border:1px solid black;">2</td>
        </tr>
        <tr>
            <td style="border:1px solid black;">1 + 2</td>
            <td style="border:1px solid black;">3</td>
        </tr>
        <tr>
            <td style="border:1px solid black;">2 + 2</td>
            <td style="border:1px solid black;">4</td>
        </tr>
    </table>
    <!-- <script type="text/javascript" src="test.js"></script> -->
</body>

</html>

源码

#-*- coding:utf-8 -*-

import re

class Piece(object):
    _html = '<{tag}{attrs}>{content}{childs}</{tag}>'

    def __init__(self, tag='', attrs={}, content='', childs='', raw=None):
        self.raw     = raw            
        self.tag     = tag
        self.attrs   = attrs
        self.content = content
        self.childs  = childs

    def resolve(self):
        if self.raw:
            return self.raw
        attrs = make_attrs(self.attrs)
        self.html = self._html.format(tag=self.tag, attrs=attrs, content=self.content, childs=self.childs)
        return self.html

    def format(self, *args, **kws):
        return self.resolve().format(*args, **kws)

    def __str__(self):
        return self.resolve()

    def __invert__(self):
        return '<!-- {} -->'.format(self.resolve()) 

    # child's inject
    def __le__(self, childs):
        self.childs = ''.join([str(o) for o in childs])
        return self

    def __mul__(self, num):
        return str(self) * 2

    def __getattr__(self, attr):
        return self.attrs.get(attr, None)

    __repr__ = __str__

def make_attrs(attrs):
    attrs = '' + ' '.join(['{}="{}"'.format(k, v) for k, v in attrs.items()])
    attrs = ' {}'.format(attrs) if attrs else ''
    return attrs

tag_flg = re.compile(r'(?P<tag>[\w]+)(\#(?P<id>[\w]+))?(\.(?P<class>[\w\s]+))?')

def h(tag, content='', **attrs):
    if tag:
        m = re.match(tag_flg, tag)
        if m:
            groups =  m.groupdict()
            if groups['id'] is not None:
                attrs['id'] = groups['id']
            if groups['class'] is not None:
                attrs['class'] = groups['class']
            if groups['tag'] is not None:
                tag = groups['tag']
    childs     = attrs.pop('c', [])
    child_html = ''.join([str(c) for c in childs])

    return Piece(tag, attrs, content, child_html)

def hmap(tag, content='', datas=None, **attrs):
    if datas is None:
        datas = []
    htmls = []
    for data in datas:
        html = Piece._html.format(
            tag=tag,
            attrs=make_attrs(attrs),
            content=content.replace('{?}', str(data)),
            childs=''
        )
        htmls.append(html)
    return ''.join(htmls)

def hcss(path):
    return Piece(raw='<link rel="stylesheet" type="text/css" href="{}">'.format(path))

def hjs(path):
    return Piece(raw='<script type="text/javascript" src="{}"></script>'.format(path))

def hc(comment):
    return '<!-- {} -->'.format(comment)

def heach(iterable, func):
    res = []
    if isinstance(iterable, list):
        for value in iterable:
            res.append(str(func(value)))
    elif isinstance(iterable, dict):
        for key, value in iterable.items():
            res.append(str(func(key, value)))
    return ''.join(res)

def hfor(times, func, **injects):
    res = []
    for i in range(times):
        res.append(func(i))
    return ''.join(res)


def _main():
    def build(piece, path):
        buf = piece.resolve()
        with open(path, 'w') as fp:
            fp.write(buf)

    datas = [
        ['1 + 1', 2],
        ['1 + 2', 3],
        ['2 + 2', 4]
    ]
    build(h('html') <= [
            h('head') <= [
                h('title', 'My Title'),
                h('meta', charset='utf-8'),
                hc('This is comment'),
                hcss('test.css')
            ],
            h('body#main.class1 class2') <= [
                h('h1', 'HtmlGen') * 2,
                h('ul', extra=1) <= [
                    hmap('li', 'I am {?}!', [1,2,3,4,5])
                ],
                h('ul') <= [
                    hfor(5, lambda i: h('li', '{}').format(chr(ord(str(i)) + 17)))
                ],
                h('table') <= [
                    h('tr') <= [
                        h('td'),
                        heach({'name': 'result'}, lambda k, v: h('td', v))
                    ],
                    heach(datas, lambda v:
                        h('tr') <= [
                            heach(v, lambda v: h('td', v, style='border:1px solid black;')),
                        ]
                    )
                ],
                ~ hjs('test.js') # comment,too
            ],
        ], 'test.html')

if __name__ == '__main__':
    _main()