<filename>zsh/gitstatus.py
#! /usr/ibn/env python2
# -*- encoding=utf-8 -*-
from __future__ import print_function
from subprocess import Popen, PIPE
import os


import serverlog
log = serverlog.inst()


def getGitStat(d):
    gitstat = Popen(
        ['git', 'status', '--short', '--branch'],
        stdout=PIPE, stderr=PIPE, cwd=d)
    stat, error = gitstat.communicate()

    error_string = error.decode('utf-8')
    if 'fatal: Not a git repository' in error_string:
        log.warn("not a git repo [%s]" % (d))
        raise Exception("not a git repo")
    return stat


def getGitHead(d):
    githead = Popen(
        ['git', 'rev-parse', '--short', 'HEAD'],
        stdout=PIPE, stderr=PIPE, cwd=d)
    commit = githead.communicate()
    ret = commit[0].strip()
    return ret


def probeBranch(s, d):
    """
    >>> probeBranch('## 7094...c [ahead 1]')
    ('7094', 'c', 1, 0)
    >>> probeBranch('## HEAD (no branch)')
    ('HEAD', '', 0, 0)
    >>> probeBranch('## 7094...c [ahead 1, behind 1]')
    ('7094', 'c', 1, 1)
    """
    br, rmt, head, behind = "", "", 0, 0
    s = s[3:]
    initialHead = "Initial commit on "
    if s.startswith(initialHead):
        br = "Init"
        return br, rmt, head, behind
    arr = s.split("...")
    br = arr[0]
    # remove () content
    idx = br.find(" (")
    if idx != -1:
        br = br[0:idx]
        if br == 'HEAD':
            br = getGitHead(d)
    try:
        rmt = arr[1]
    except IndexError:
        return br, rmt, head, behind
    rmt = rmt.split()[0]

    def getNumber(s, prefix, suffix):
        head = s.find(prefix)
        if head is -1:
            return 0
        head += len(prefix)
        tail = s.find(suffix, head)
        if tail is -1:
            return 0
        sub = s[head:tail]
        try:
            return int(sub)
        except ValueError:
            return 0

    head = getNumber(s, "[ahead ", "]")
    if head == 0:
        head = getNumber(s, "[ahead ", ",")
    behind = getNumber(s, "behind ", "]")
    return br, rmt, head, behind


def probeLines(lines):
    Untrack, uModify, uDelete = 0, 0, 0
    Modify, Add, Delete = 0, 0, 0
    for line in lines:
        if line is "":
            continue
        X, Y = line[0], line[1]
        if X is '!':
            continue
        Untrack += X is '?'
        if X is " " or X is "M":
            uModify += Y is 'M'
            uDelete += Y is 'D'
        Modify += X is 'M'
        Add += X is 'A'
        Delete += X is 'D'
    return (Modify, Add, Delete), (uModify, uDelete, Untrack)


class RepoSt:
    clean, unsync, stage, dirty = range(4)


def NotZero(v, sym=""):
    return sym + str(v) if v is not 0 else ""


class Symb():
    branch = ""
    remove = "∆"
    remote = "⌃"
    # forward = "⌃"
    # backward = "⌄"
# "⌃⌄"
# '><'
# "∧∨"
    forward = '>'
    backward = "<"
    modify = "±"
    add = "✚"
    delt = "✖"
    uModify = modify
    uDelete = "x"
    # Untrack = "✚"
    # Untrack = "."
    Untrack = "?"
    delimiter = "•"


def fmtBranch(brRet):
    br, rmt, head, behind = brRet
    s = Symb.branch + br
    log.debug("branch %s %s %s %s" % (br, rmt, head, behind))
    if rmt != "origin/master" and rmt is not "":
        idx = rmt.rfind("/")
        if idx != -1:
            rmt = rmt[idx + 1:]
        if rmt != br:
            s += Symb.remote + rmt
    s += NotZero(head, Symb.forward)
    s += NotZero(behind, Symb.backward)
    status = RepoSt.unsync if head != 0 or behind != 0 else RepoSt.clean
    return s, status


def fmtStage(num):
    s = ""
    Modify, Add, Delete = num
    symbol = [Symb.modify, Symb.add, Symb.delt]
    status = RepoSt.stage if sum(num) is not 0 else RepoSt.clean
    for v, sym in zip(num, symbol):
        s += NotZero(v, sym)
    return s, status


def fmtDirty(num):
    s = ""
    uModify, uDelete, Untrack = num
    symbol = [Symb.uModify, Symb.uDelete, Symb.Untrack]
    # get status skip untrack
    status = RepoSt.dirty if sum(num[0:2]) is not 0 else RepoSt.clean
    for v, sym in zip(num, symbol):
        s += NotZero(v, sym)
    return s, status


def combSeg(br, stage, dirty):
    arr = (fmtBranch(br), fmtStage(stage), fmtDirty(dirty))
    seg, st = zip(*arr)

    def NotNull(v, sym=""):
        return sym + str(v) if v != "" else ""
    s = seg[0]
    if seg[1] != "" or seg[2] != "":
        s += Symb.delimiter + seg[1]
        s += Symb.delimiter + seg[2]
    return s, max(st)


def main(d):
    try:
        stat = getGitStat(d)
    except:
        log.warn("getGitStat on d=[%s] fail" % (d))
        return ""
    lines = stat.split('\n')
    br = probeBranch(lines[0], d)
    stage, dirty = probeLines(lines[1:])
    ret = combSeg(br, stage, dirty)
    return ret[0] + " " + str(ret[1])


if __name__ == "__main__":
    ret = main(os.getcwd())
    print(ret)

<filename>zsh/gitstatus.py
#! /usr/ibn/env python2
# -*- encoding=utf-8 -*-
from __future__ import print_function
from subprocess import Popen, PIPE
import os


import serverlog
log = serverlog.inst()


def getGitStat(d):
    gitstat = Popen(
        ['git', 'status', '--short', '--branch'],
        stdout=PIPE, stderr=PIPE, cwd=d)
    stat, error = gitstat.communicate()

    error_string = error.decode('utf-8')
    if 'fatal: Not a git repository' in error_string:
        log.warn("not a git repo [%s]" % (d))
        raise Exception("not a git repo")
    return stat


def getGitHead(d):
    githead = Popen(
        ['git', 'rev-parse', '--short', 'HEAD'],
        stdout=PIPE, stderr=PIPE, cwd=d)
    commit = githead.communicate()
    ret = commit[0].strip()
    return ret


def probeBranch(s, d):
    """
    >>> probeBranch('## 7094...c [ahead 1]')
    ('7094', 'c', 1, 0)
    >>> probeBranch('## HEAD (no branch)')
    ('HEAD', '', 0, 0)
    >>> probeBranch('## 7094...c [ahead 1, behind 1]')
    ('7094', 'c', 1, 1)
    """
    br, rmt, head, behind = "", "", 0, 0
    s = s[3:]
    initialHead = "Initial commit on "
    if s.startswith(initialHead):
        br = "Init"
        return br, rmt, head, behind
    arr = s.split("...")
    br = arr[0]
    # remove () content
    idx = br.find(" (")
    if idx != -1:
        br = br[0:idx]
        if br == 'HEAD':
            br = getGitHead(d)
    try:
        rmt = arr[1]
    except IndexError:
        return br, rmt, head, behind
    rmt = rmt.split()[0]

    def getNumber(s, prefix, suffix):
        head = s.find(prefix)
        if head is -1:
            return 0
        head += len(prefix)
        tail = s.find(suffix, head)
        if tail is -1:
            return 0
        sub = s[head:tail]
        try:
            return int(sub)
        except ValueError:
            return 0

    head = getNumber(s, "[ahead ", "]")
    if head == 0:
        head = getNumber(s, "[ahead ", ",")
    behind = getNumber(s, "behind ", "]")
    return br, rmt, head, behind


def probeLines(lines):
    Untrack, uModify, uDelete = 0, 0, 0
    Modify, Add, Delete = 0, 0, 0
    for line in lines:
        if line is "":
            continue
        X, Y = line[0], line[1]
        if X is '!':
            continue
        Untrack += X is '?'
        if X is " " or X is "M":
            uModify += Y is 'M'
            uDelete += Y is 'D'
        Modify += X is 'M'
        Add += X is 'A'
        Delete += X is 'D'
    return (Modify, Add, Delete), (uModify, uDelete, Untrack)


class RepoSt:
    clean, unsync, stage, dirty = range(4)


def NotZero(v, sym=""):
    return sym + str(v) if v is not 0 else ""


class Symb():
    branch = ""
    remove = "∆"
    remote = "⌃"
    # forward = "⌃"
    # backward = "⌄"
# "⌃⌄"
# '><'
# "∧∨"
    forward = '>'
    backward = "<"
    modify = "±"
    add = "✚"
    delt = "✖"
    uModify = modify
    uDelete = "x"
    # Untrack = "✚"
    # Untrack = "."
    Untrack = "?"
    delimiter = "•"


def fmtBranch(brRet):
    br, rmt, head, behind = brRet
    s = Symb.branch + br
    log.debug("branch %s %s %s %s" % (br, rmt, head, behind))
    if rmt != "origin/master" and rmt is not "":
        idx = rmt.rfind("/")
        if idx != -1:
            rmt = rmt[idx + 1:]
        if rmt != br:
            s += Symb.remote + rmt
    s += NotZero(head, Symb.forward)
    s += NotZero(behind, Symb.backward)
    status = RepoSt.unsync if head != 0 or behind != 0 else RepoSt.clean
    return s, status


def fmtStage(num):
    s = ""
    Modify, Add, Delete = num
    symbol = [Symb.modify, Symb.add, Symb.delt]
    status = RepoSt.stage if sum(num) is not 0 else RepoSt.clean
    for v, sym in zip(num, symbol):
        s += NotZero(v, sym)
    return s, status


def fmtDirty(num):
    s = ""
    uModify, uDelete, Untrack = num
    symbol = [Symb.uModify, Symb.uDelete, Symb.Untrack]
    # get status skip untrack
    status = RepoSt.dirty if sum(num[0:2]) is not 0 else RepoSt.clean
    for v, sym in zip(num, symbol):
        s += NotZero(v, sym)
    return s, status


def combSeg(br, stage, dirty):
    arr = (fmtBranch(br), fmtStage(stage), fmtDirty(dirty))
    seg, st = zip(*arr)

    def NotNull(v, sym=""):
        return sym + str(v) if v != "" else ""
    s = seg[0]
    if seg[1] != "" or seg[2] != "":
        s += Symb.delimiter + seg[1]
        s += Symb.delimiter + seg[2]
    return s, max(st)


def main(d):
    try:
        stat = getGitStat(d)
    except:
        log.warn("getGitStat on d=[%s] fail" % (d))
        return ""
    lines = stat.split('\n')
    br = probeBranch(lines[0], d)
    stage, dirty = probeLines(lines[1:])
    ret = combSeg(br, stage, dirty)
    return ret[0] + " " + str(ret[1])


if __name__ == "__main__":
    ret = main(os.getcwd())
    print(ret)

#! /usr/ibn/env python2 # -*- encoding=utf-8 -*- >>> probeBranch('## 7094...c [ahead 1]')     ('7094', 'c', 1, 0)     >>> probeBranch('## HEAD (no branch)')     ('HEAD', '', 0, 0)     >>> probeBranch('## 7094...c [ahead 1, behind 1]')     ('7094', 'c', 1, 1) # remove () content # forward = "⌃" # backward = "⌄" # "⌃⌄" # '><' # "∧∨" # Untrack = "✚" # Untrack = "." # get status skip untrack