Morpheus / Mbed OS mbed-Client-Morpheus-hg

Dependencies:   mbed-os

neo.py

Committer:
Christopher Haster
Date:
2016-03-30
Revision:
17:1e487c450f06
Parent:
16:b54f256e339e
Child:
18:1b4252106474

File content as of revision 17:1e487c450f06:

#!/usr/bin/env python

import argparse
import sys
import re
import subprocess
import os
import contextlib
import collections
import shutil
from itertools import *

# Subparser handling
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

def subcommand(name, *args, **kwargs):
    def subcommand(command):
        subparser = subparsers.add_parser(name, **kwargs)
    
        for arg in args:
            if arg.endswith('?'):
                subparser.add_argument(arg.strip('?'), nargs='?')
            elif arg.endswith('*'):
                pass
            else:
                subparser.add_argument(arg)
    
        def thunk(parsed_args):
            ordered_args = [vars(parsed_args)[arg.strip('?*')]
                            if not arg.endswith('*') else remainder
                            for arg in args]
            return command(*ordered_args)
    
        subparser.set_defaults(command=thunk)
        return command
    return subcommand

# Process execution
class ProcessException(Exception):
    pass

def popen(command, stdin=None, **kwargs):
    # print for debugging
    print ' '.join(command)
    proc = subprocess.Popen(command, **kwargs)

    if proc.wait() != 0:
        raise ProcessException(proc.returncode)

def pquery(command, stdin=None, **kwargs):
    proc = subprocess.Popen(command, stdout=subprocess.PIPE, **kwargs)
    stdout, _ = proc.communicate(stdin)

    if proc.returncode != 0:
        raise ProcessException(proc.returncode)

    return stdout

# Directory navigation
@contextlib.contextmanager
def cd(newdir):
    prevdir = os.getcwd()
    os.chdir(newdir)
    try:
        yield
    finally:
        os.chdir(prevdir)

def iterlibs(dir=None):
    for root, dirs, files in os.walk(dir or os.getcwd()):
        if os.path.basename(root).startswith('.'):
            del dirs[:]
            continue

        for file in files:
            if file.startswith('.'):
                continue
            elif file.endswith('.lib'):
                with open(os.path.join(root, file)) as f:
                    yield f.read().strip(), os.path.join(root, file[:-4])
                if file[:-4] in dirs:
                    dirs.remove(file[:-4])

def savelib(repo):
    print repo.name, '->', repo.url

    with open(repo.lib, 'w') as f:
        f.write(repo.url + '\n')


# Handling for multiple version controls
class Repo(object):
    def __init__(self, path=None):
        self.path = path or os.getcwd()
        self.name = os.path.basename(self.path)
        self.update()

    @classmethod
    def fromurl(cls, url, name=None):
        repo = cls.__new__(cls)

        m = re.match('^(.*/([+a-zA-Z0-9_-]+)/?)(?:#(.*))?$', url)
        repo.repo = m.group(1)
        repo.name = name or m.group(2)
        repo.hash = m.group(3)

        repo.path = os.path.join(os.getcwd(), repo.name)
        return repo

    @property
    def lib(self):
        return self.path + '.lib'

    @property
    def url(self):
        if self.repo:
            if self.hash:
                return self.repo + '#' + self.hash
            else:
                return self.repo

    def update(self):
        if os.path.isdir(self.path):
            self.type = self.gettype()
            self.hash = self.gethash()

        if os.path.isfile(self.lib):
            with open(self.lib) as f:
                temp = Repo.fromurl(f.read())
                self.repo = temp.repo

    def gettype(self):
        for type, dir in [('hg', '.hg'), ('git', '.git')]:
            if os.path.isdir(os.path.join(self.path, dir)):
                return type

    def gethash(self):
        with cd(self.path):
            if self.type == 'git':
                return pquery(['git', 'rev-parse', '--short', 'HEAD']).strip()
            elif self.type == 'hg':
                return pquery(['hg', 'id', '-i']).strip()

# Clone command
@subcommand('import', 'url', 'name?',
    help='recursively import a repository')
def import_(url, name=None):
    repo = Repo.fromurl(url, name)

    for type in ['hg', 'git']:
        try:
            popen([type, 'clone', repo.repo, repo.path])
            break
        except:
            pass

    repo.type = repo.gettype()

    if repo.hash:
        with cd(repo.path):
            popen([repo.type, 'checkout', repo.hash])

    repo.update()

    with cd(repo.path):
        for url, lib in iterlibs():
            import_(url, lib)

# Deploy command
@subcommand('deploy',
    help='recursively import libraries in current directory')
def deploy():
    for url, lib in iterlibs():
        import_(url, lib)

# Install/uninstall command
@subcommand('add', 'url', 'name?',
    help='add a library to the current repository')
def add(url, name):
    cwd = Repo()
    repo = Repo.fromurl(url, name)

    import_(url)

    repo.update()
    savelib(repo)

    popen([cwd.type, 'add', repo.lib])

@subcommand('remove', 'library',
    help='remove a library from the current repository folder')
def remove(library):
    cwd = Repo()
    repo = Repo(library)

    popen([cwd.type, 'rm', '-f', repo.lib])

    try:
        if cwd.type == 'hg':
            os.remove(repo.lib)

        shutil.rmtree(repo.path)
    except OSError:
        pass

# Publish command
@subcommand('publish',
    help='recursively publish changes to remote repositories')
def publish():
    cwd = Repo()

    for url, lib in iterlibs():
        if os.path.isdir(lib):
            with cd(lib):
                publish()

    synch()

    if cwd.type == 'hg':
        modified = pquery(['hg', 'status'])
    elif cwd.type == 'git':
        modified = pquery(['git', 'diff', '--name-only', 'HEAD'])

    if modified:
        print cwd.path
        print 'Uncommitted changes in %s' % cwd.name
        raw_input('Press enter to commit/push')

        popen([cwd.type, 'commit'] + (['-a'] if cwd.type == 'git' else []))
        popen([cwd.type, 'push'] + (['-a'] if cwd.type == 'git' else []))

# Synch command
@subcommand('synch',
    help='synchronize lib files')
def synch():
    cwd = Repo()

    for url, lib in iterlibs():
        repo = Repo.fromurl(url, lib)
        repo.update()
        if lib == repo.url:
            continue

        savelib(repo)

        if cwd.type == 'git':
            popen([cwd.type, 'add', repo.lib])

# Compile command
@subcommand('compile', 'args*',
    help='compile project using workspace_tools')
def compile(args):
    cwd = Repo()

    if not os.path.isdir('mbed-os'):
        sys.stderr.write('Warning! mbed-os not found?')
        sys.exit(-1)

    if not os.path.isfile('mbed_settings.py'):
        shutil.copy('mbed-os/tools/settings.py', 'mbed_settings.py')

    if os.path.isfile('MACROS.txt'):
        with open('MACROS.txt') as f:
            macros = f.read().splitlines()

    env = os.environ.copy()
    env['PYTHONPATH'] = '.'
    popen(['python', 'mbed-os/tools/make.py']
        + list(chain.from_iterable(izip(repeat('-D'), macros)))
        + ['--source=%s' % cwd.path,
           '--build=%s' % os.path.join(cwd.path, '.build')]
        + args,
        env=env)

# Export command
@subcommand('export', 'args*',
    help='generate project files')
def export(args):
    cwd = Repo()

    if not os.path.isdir('mbed-os'):
        sys.stderr.write('Warning! mbed-os not found?')
        sys.exit(-1)

    if not os.path.isfile('mbed_settings.py'):
        shutil.copy('mbed-os/tools/settings.py', 'mbed_settings.py')

    popen(['python', 'mbed-os/tools/project.py',
           '--source=%s' % cwd.path]
        + args)

# Parse/run command
args, remainder = parser.parse_known_args()
status = args.command(args)
sys.exit(status or 0)