#!/usr/bin/env python3
"""zhlang 包仓库工具(v2.40)

用法:
  python3 -I tools/发布包.py 打包 <包源目录> <仓库根>
  python3 -I tools/发布包.py 索引 <仓库根>

包源目录须含 发布.json:
  {"名字":"demo","版本":"1.0.0","说明":"…","依赖":{"其他包":"1.0.0"},
   "需要编译器":"2.40"}
打包产出 <仓库根>/包/<名字>/<版本>/<名字>-<版本>.tar.b64(ustar + base64),
并把 发布.json 复制到同目录。索引 扫描全部版本,生成 <仓库根>/索引.json
(sha256 针对 base64 文本)。仓库可直接静态托管(nginx root)。
"""
import base64
import hashlib
import io
import json
import os
import sys
import tarfile


def 读发布(源目录):
    p = os.path.join(源目录, '发布.json')
    if not os.path.isfile(p):
        raise SystemExit('缺少 %s' % p)
    d = json.load(io.open(p, encoding='utf-8'))
    for k in ('名字', '版本'):
        if not isinstance(d.get(k), str) or not d[k]:
            raise SystemExit('发布.json 缺少「%s」' % k)
    if '/' in d['名字'] or '\\' in d['名字'] or d['名字'] in ('.', '..'):
        raise SystemExit('非法包名')
    return d


def 打包(源目录, 仓库根):
    元 = 读发布(源目录)
    名, 版 = 元['名字'], 元['版本']
    目标 = os.path.join(仓库根, '包', 名, 版)
    os.makedirs(目标, exist_ok=True)
    tar = os.path.join(目标, '%s-%s.tar' % (名, 版))
    with tarfile.open(tar, 'w', format=tarfile.USTAR_FORMAT) as t:
        for 根, 目录们, 文件们 in os.walk(源目录):
            目录们[:] = [d for d in 目录们 if d not in ('.git', '__pycache__')]
            for f in sorted(文件们):
                if f == '发布.json':
                    continue
                全 = os.path.join(根, f)
                相对 = os.path.relpath(全, 源目录).replace(os.sep, '/')
                t.add(全, arcname=相对)
    b64 = tar + '.b64'
    io.open(b64, 'w', encoding='ascii').write(
        base64.b64encode(open(tar, 'rb').read()).decode())
    os.remove(tar)
    io.open(os.path.join(目标, '发布.json'), 'w', encoding='utf-8').write(
        json.dumps(元, ensure_ascii=False, indent=1))
    print('已打包 %s %s -> %s' % (名, 版, b64))


def 生成索引(仓库根):
    包目录 = os.path.join(仓库根, '包')
    索引 = {'格式': 1, '包': {}}
    if os.path.isdir(包目录):
        for 名 in sorted(os.listdir(包目录)):
            版目录 = os.path.join(包目录, 名)
            if not os.path.isdir(版目录):
                continue
            条目 = {'最新': '', '说明': '', '版本': {}}
            版本们 = []
            for 版 in sorted(os.listdir(版目录)):
                d = os.path.join(版目录, 版)
                p = os.path.join(d, '发布.json')
                if not os.path.isfile(p):
                    continue
                元 = json.load(io.open(p, encoding='utf-8'))
                b64 = os.path.join(d, '%s-%s.tar.b64' % (名, 版))
                if not os.path.isfile(b64):
                    raise SystemExit('缺包文件 %s' % b64)
                h = hashlib.sha256(open(b64, 'rb').read()).hexdigest()
                文件 = '包/%s/%s/%s-%s.tar.b64' % (名, 版, 名, 版)
                条目['版本'][版] = {
                    '文件': 文件, 'sha256': h, '编码': 'base64',
                    '依赖': 元.get('依赖', {}) or {},
                    '需要编译器': 元.get('需要编译器', ''),
                }
                条目['说明'] = 条目['说明'] or 元.get('说明', '')
                版本们.append(版)
            if not 版本们:
                continue
            # 版本排序:数字段逐段比较,非数字按字符串
            def 键(v):
                return [(0, int(x)) if x.isdigit() else (1, x)
                        for x in v.replace('-', '.').split('.')]
            条目['最新'] = sorted(版本们, key=键)[-1]
            索引['包'][名] = 条目
    out = os.path.join(仓库根, '索引.json')
    io.open(out, 'w', encoding='utf-8').write(
        json.dumps(索引, ensure_ascii=False, indent=1))
    print('已生成 %s(%d 个包)' % (out, len(索引['包'])))


def main():
    if len(sys.argv) < 2 or sys.argv[1] not in ('打包', '索引'):
        raise SystemExit(__doc__)
    if sys.argv[1] == '打包':
        if len(sys.argv) != 4:
            raise SystemExit(__doc__)
        打包(sys.argv[2], sys.argv[3])
    else:
        if len(sys.argv) != 3:
            raise SystemExit(__doc__)
        生成索引(sys.argv[2])


if __name__ == '__main__':
    main()
