Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- """
- atsbuild
- ========
- Author: Ryan King <hello@ryanking.com>
- Usage
- -----
- make [ -f buildfile ] [ command ]
- Description
- -----------
- Utility to build ATS projects.
- Command Line Options
- --------------------
- -f, --file buildfile
- Use buildfile as the build file.
- Commands
- --------
- build
- Compile the current project.
- clean
- Remove the target directory with build output.
- init
- Create a new ATS project in the current directory.
- install
- Install an ATS package via npm.
- new
- Create a new ATS project.
- run
- Build and execute the entry file.
- update
- Update the project dependencies.
- package
- Create a package.json file for publishing.
- publish
- Publish the project to NPM.
- search
- Search NPM for an ATS package.
- """
- from __future__ import print_function
- import argparse as ap
- import configparser as cp
- from difflib import get_close_matches
- from os import path
- from subprocess import PIPE, Popen
- from sys import stderr
- VALID_COMMANDS = [
- 'build', 'clean', 'init', 'install', 'new',
- 'run', 'update', 'package', 'publish', 'search'
- ]
- def info(task, msg):
- assert isinstance(msg, str) and isinstance(task, str)
- print('\033[1;36m%10s\033[0m %s' % (task, msg))
- def fatal(msg, do_exit=True):
- """Prints out an error message then exits the program.
- """
- assert isinstance(msg, str)
- print('atsmake:\033[1;31m error:\033[0m ' + msg, file=stderr)
- if do_exit:
- exit(1)
- def parse_args():
- """Uses the ArgumentParser module to parse the command line arguments.
- """
- parser = ap.ArgumentParser(
- prog='atsmake',
- description='Utility to build ATS projects.'
- )
- parser.add_argument(
- '-f', '--file',
- default='package.bats', dest='buildfile', metavar='buildfile',
- help='Use \033[4mbuildfile\033[0m as the build file.'
- )
- parser.add_argument(
- 'command', nargs='+',
- help='The command to execute.'
- )
- args = parser.parse_args()
- return (args.buildfile, args.command)
- def get_npm_versions(dep):
- """Checks the online version of an npm package.
- """
- cmd = ['npm', 'show', dep, 'versions']
- try:
- proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
- proc.wait()
- versions = proc.stdout.readline().strip('\n')
- err = proc.stderr.readline()
- if err:
- fatal('%s: package not found' % dep)
- return versions[1:-1].replace(' ', '').replace('\'', '').split(',')
- except FileNotFoundError:
- fatal('npm not installed')
- def extract_ver(dep, version):
- """Extracts the major, minor, and revision from a dotted version, and
- converts them to integers.
- """
- [major, *rest] = version.split('.')
- [minor, *rest] = rest if rest else [None, []]
- [patch, *rest] = rest if rest else [None, []]
- if rest:
- fatal('%s: invalid version requirement: %s' % (dep, version))
- major = int(major) if major != '*' else '*'
- if not minor or minor != '*':
- minor = int(minor)
- if not patch or patch != '*':
- patch = int(patch)
- return (major, minor, patch)
- def vers_to_num(dep, version):
- [major, *rest] = version.split('.')
- [minor, *rest] = rest if rest else ['0', []]
- [patch, *rest] = rest if rest else ['0', []]
- if rest:
- fatal('%s: invalid version requirement: %s' % (dep, version))
- major = '0' + major if len(major) == 1 else major
- minor = '0' + minor if len(minor) == 1 else minor
- patch = '0' + patch if len(patch) == 1 else patch
- return int(major + minor + patch)
- def install_deps(deps):
- """Installs all the required dependencies.
- """
- for (dep, req_ver) in deps:
- req_ver = req_ver[1:-1]
- [req, *ver] = req_ver
- if req.isnumeric():
- req = '*' if '*' in ver else '!'
- ver = req_ver
- elif req_ver[:2] == '>=':
- req = '>='
- ver = ver[1:]
- ver = ''.join(ver)
- if req not in '!^~*<>=' or ('*' in req_ver and req not in '!*'):
- fatal('%s: invalid version requirement: %s' % (dep, req_ver))
- hit_wc = False
- for c in ver:
- if (hit_wc and c in '*0123456789') or c not in '*.0123456789':
- fatal('%s: invalid version requirement: %s' % (dep, req_ver))
- hit_wc = True if c == '*' else hit_wc
- npm_vers = get_npm_versions(dep)
- (major, minor, patch) = extract_ver(dep, ver)
- vers_joint = vers_to_num(dep, ver) if req != '*' else 0
- install_ver = ''
- if req in '!=':
- if ver in npm_vers:
- install_ver = ver
- else:
- fatal('%s: version %s not available' % (dep, req_ver))
- elif req == '^':
- for npm_ver in reversed(npm_vers):
- (npm_mj, npm_mn, npm_pt) = extract_ver(dep, npm_ver)
- mn = minor if minor and major == 0 else npm_mn
- pt = patch if patch and minor == 0 else npm_pt
- if npm_mj == major and npm_mn == mn and npm_pt == pt:
- install_ver = npm_ver
- break
- if not install_ver:
- fatal('%s: version %s not available' % (dep, req_ver))
- elif req == '~':
- for npm_ver in reversed(npm_vers):
- (npm_mj, npm_mn, npm_pt) = extract_ver(dep, npm_ver)
- mn = minor if minor else npm_mn
- pt = patch if patch else npm_pt
- if npm_mj == major and npm_mn == mn and npm_pt >= pt:
- install_ver = npm_ver
- break
- if not install_ver:
- fatal('%s: version %s not available' % (dep, req_ver))
- elif req == '*':
- for npm_ver in reversed(npm_vers):
- (npm_mj, npm_mn, npm_pt) = extract_ver(dep, npm_ver)
- mj = major if major != '*' else npm_mj
- mn = minor if minor and minor != '*' else npm_mn
- pt = patch if patch and patch != '*' else npm_pt
- if mj == npm_mj and mn == npm_mn and pt == npm_pt:
- install_ver = npm_ver
- break
- if not install_ver:
- fatal('%s: version %s not available' % (dep, req_ver))
- elif req == '>=':
- for npm_ver in reversed(npm_vers):
- npm_joint = vers_to_num(dep, npm_ver)
- if vers_joint >= npm_joint:
- install_ver = npm_ver
- break
- if not install_ver:
- fatal('%s: version %s not available' % (dep, req_ver))
- elif req == '>':
- for npm_ver in reversed(npm_vers):
- npm_joint = vers_to_num(dep, npm_ver)
- if vers_joint > npm_joint:
- install_ver = npm_ver
- break
- if not install_ver:
- fatal('%s: version %s not available' % (dep, req_ver))
- elif req == '<':
- for npm_ver in reversed(npm_vers):
- npm_joint = vers_to_num(dep, npm_ver)
- if vers_joint < npm_joint:
- install_ver = npm_ver
- break
- if not install_ver:
- fatal('%s: version %s not available' % (dep, req_ver))
- else:
- fatal('%s: version %s is corrupted' % (dep, req_ver))
- info('downloading', '%s version %s' % (dep, install_ver))
- try:
- proc = Popen(['npm', 'install', '%s@%s' % (dep, install_ver)])
- proc.wait()
- except FileNotFoundError:
- fatal('npm not installed')
- info('installed', '%s version %s' % (dep, install_ver))
- def main():
- """Main function to read the config file, and execute each target.
- """
- (build_file, [cmd, *args]) = parse_args()
- if not path.isfile(build_file):
- fatal('%s: no such file or directory' % build_file)
- if cmd not in VALID_COMMANDS:
- fatal('no such command: %s' % cmd, do_exit=False)
- suggestions = get_close_matches(cmd, VALID_COMMANDS)
- if suggestions:
- print('\n Did you mean \'%s\'\n' % suggestions[0])
- exit(1)
- config = cp.ConfigParser()
- config.read(build_file)
- if 'package' not in config:
- fatal('%s: missing \'package\' section' % build_file)
- if 'dependencies' not in config:
- fatal('%s: missing \'dependencies\' section' % build_file)
- if 'name' not in config['package']:
- fatal('%s: missing \'name\' value in the \'package\' section'
- % build_file)
- if cmd == 'build':
- install_deps(config.items('dependencies'))
- if __name__ == '__main__':
- main()
- # -*-Python-*-
- # End of [atsmake]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement