Advertisement
Guest User

Untitled

a guest
Mar 27th, 2017
54
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.61 KB | None | 0 0
  1. #!/usr/bin/env python
  2. """
  3. atsbuild
  4. ========
  5. Author: Ryan King <hello@ryanking.com>
  6.  
  7. Usage
  8. -----
  9. make [ -f buildfile ] [ command ]
  10.  
  11. Description
  12. -----------
  13. Utility to build ATS projects.
  14.  
  15. Command Line Options
  16. --------------------
  17. -f, --file buildfile
  18. Use buildfile as the build file.
  19.  
  20. Commands
  21. --------
  22. build
  23. Compile the current project.
  24.  
  25. clean
  26. Remove the target directory with build output.
  27.  
  28. init
  29. Create a new ATS project in the current directory.
  30.  
  31. install
  32. Install an ATS package via npm.
  33.  
  34. new
  35. Create a new ATS project.
  36.  
  37. run
  38. Build and execute the entry file.
  39.  
  40. update
  41. Update the project dependencies.
  42.  
  43. package
  44. Create a package.json file for publishing.
  45.  
  46. publish
  47. Publish the project to NPM.
  48.  
  49. search
  50. Search NPM for an ATS package.
  51. """
  52.  
  53. from __future__ import print_function
  54. import argparse as ap
  55. import configparser as cp
  56. from difflib import get_close_matches
  57. from os import path
  58. from subprocess import PIPE, Popen
  59. from sys import stderr
  60.  
  61. VALID_COMMANDS = [
  62. 'build', 'clean', 'init', 'install', 'new',
  63. 'run', 'update', 'package', 'publish', 'search'
  64. ]
  65.  
  66. def info(task, msg):
  67. assert isinstance(msg, str) and isinstance(task, str)
  68. print('\033[1;36m%10s\033[0m %s' % (task, msg))
  69.  
  70. def fatal(msg, do_exit=True):
  71. """Prints out an error message then exits the program.
  72. """
  73. assert isinstance(msg, str)
  74. print('atsmake:\033[1;31m error:\033[0m ' + msg, file=stderr)
  75. if do_exit:
  76. exit(1)
  77.  
  78. def parse_args():
  79. """Uses the ArgumentParser module to parse the command line arguments.
  80. """
  81. parser = ap.ArgumentParser(
  82. prog='atsmake',
  83. description='Utility to build ATS projects.'
  84. )
  85.  
  86. parser.add_argument(
  87. '-f', '--file',
  88. default='package.bats', dest='buildfile', metavar='buildfile',
  89. help='Use \033[4mbuildfile\033[0m as the build file.'
  90. )
  91. parser.add_argument(
  92. 'command', nargs='+',
  93. help='The command to execute.'
  94. )
  95.  
  96. args = parser.parse_args()
  97.  
  98. return (args.buildfile, args.command)
  99.  
  100.  
  101. def get_npm_versions(dep):
  102. """Checks the online version of an npm package.
  103. """
  104. cmd = ['npm', 'show', dep, 'versions']
  105.  
  106. try:
  107. proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
  108. proc.wait()
  109.  
  110. versions = proc.stdout.readline().strip('\n')
  111. err = proc.stderr.readline()
  112.  
  113. if err:
  114. fatal('%s: package not found' % dep)
  115.  
  116. return versions[1:-1].replace(' ', '').replace('\'', '').split(',')
  117. except FileNotFoundError:
  118. fatal('npm not installed')
  119.  
  120.  
  121. def extract_ver(dep, version):
  122. """Extracts the major, minor, and revision from a dotted version, and
  123. converts them to integers.
  124. """
  125. [major, *rest] = version.split('.')
  126. [minor, *rest] = rest if rest else [None, []]
  127. [patch, *rest] = rest if rest else [None, []]
  128.  
  129. if rest:
  130. fatal('%s: invalid version requirement: %s' % (dep, version))
  131.  
  132. major = int(major) if major != '*' else '*'
  133.  
  134. if not minor or minor != '*':
  135. minor = int(minor)
  136.  
  137. if not patch or patch != '*':
  138. patch = int(patch)
  139.  
  140. return (major, minor, patch)
  141.  
  142.  
  143. def vers_to_num(dep, version):
  144. [major, *rest] = version.split('.')
  145. [minor, *rest] = rest if rest else ['0', []]
  146. [patch, *rest] = rest if rest else ['0', []]
  147.  
  148. if rest:
  149. fatal('%s: invalid version requirement: %s' % (dep, version))
  150.  
  151. major = '0' + major if len(major) == 1 else major
  152. minor = '0' + minor if len(minor) == 1 else minor
  153. patch = '0' + patch if len(patch) == 1 else patch
  154.  
  155. return int(major + minor + patch)
  156.  
  157.  
  158. def install_deps(deps):
  159. """Installs all the required dependencies.
  160. """
  161. for (dep, req_ver) in deps:
  162. req_ver = req_ver[1:-1]
  163. [req, *ver] = req_ver
  164. if req.isnumeric():
  165. req = '*' if '*' in ver else '!'
  166. ver = req_ver
  167. elif req_ver[:2] == '>=':
  168. req = '>='
  169. ver = ver[1:]
  170.  
  171. ver = ''.join(ver)
  172.  
  173. if req not in '!^~*<>=' or ('*' in req_ver and req not in '!*'):
  174. fatal('%s: invalid version requirement: %s' % (dep, req_ver))
  175.  
  176. hit_wc = False
  177. for c in ver:
  178. if (hit_wc and c in '*0123456789') or c not in '*.0123456789':
  179. fatal('%s: invalid version requirement: %s' % (dep, req_ver))
  180. hit_wc = True if c == '*' else hit_wc
  181.  
  182. npm_vers = get_npm_versions(dep)
  183. (major, minor, patch) = extract_ver(dep, ver)
  184. vers_joint = vers_to_num(dep, ver) if req != '*' else 0
  185. install_ver = ''
  186.  
  187. if req in '!=':
  188. if ver in npm_vers:
  189. install_ver = ver
  190. else:
  191. fatal('%s: version %s not available' % (dep, req_ver))
  192. elif req == '^':
  193. for npm_ver in reversed(npm_vers):
  194. (npm_mj, npm_mn, npm_pt) = extract_ver(dep, npm_ver)
  195. mn = minor if minor and major == 0 else npm_mn
  196. pt = patch if patch and minor == 0 else npm_pt
  197.  
  198. if npm_mj == major and npm_mn == mn and npm_pt == pt:
  199. install_ver = npm_ver
  200. break
  201.  
  202. if not install_ver:
  203. fatal('%s: version %s not available' % (dep, req_ver))
  204. elif req == '~':
  205. for npm_ver in reversed(npm_vers):
  206. (npm_mj, npm_mn, npm_pt) = extract_ver(dep, npm_ver)
  207. mn = minor if minor else npm_mn
  208. pt = patch if patch else npm_pt
  209.  
  210. if npm_mj == major and npm_mn == mn and npm_pt >= pt:
  211. install_ver = npm_ver
  212. break
  213.  
  214. if not install_ver:
  215. fatal('%s: version %s not available' % (dep, req_ver))
  216. elif req == '*':
  217. for npm_ver in reversed(npm_vers):
  218. (npm_mj, npm_mn, npm_pt) = extract_ver(dep, npm_ver)
  219. mj = major if major != '*' else npm_mj
  220. mn = minor if minor and minor != '*' else npm_mn
  221. pt = patch if patch and patch != '*' else npm_pt
  222.  
  223. if mj == npm_mj and mn == npm_mn and pt == npm_pt:
  224. install_ver = npm_ver
  225. break
  226.  
  227. if not install_ver:
  228. fatal('%s: version %s not available' % (dep, req_ver))
  229. elif req == '>=':
  230. for npm_ver in reversed(npm_vers):
  231. npm_joint = vers_to_num(dep, npm_ver)
  232.  
  233. if vers_joint >= npm_joint:
  234. install_ver = npm_ver
  235. break
  236.  
  237. if not install_ver:
  238. fatal('%s: version %s not available' % (dep, req_ver))
  239. elif req == '>':
  240. for npm_ver in reversed(npm_vers):
  241. npm_joint = vers_to_num(dep, npm_ver)
  242.  
  243. if vers_joint > npm_joint:
  244. install_ver = npm_ver
  245. break
  246.  
  247. if not install_ver:
  248. fatal('%s: version %s not available' % (dep, req_ver))
  249. elif req == '<':
  250. for npm_ver in reversed(npm_vers):
  251. npm_joint = vers_to_num(dep, npm_ver)
  252.  
  253. if vers_joint < npm_joint:
  254. install_ver = npm_ver
  255. break
  256.  
  257. if not install_ver:
  258. fatal('%s: version %s not available' % (dep, req_ver))
  259. else:
  260. fatal('%s: version %s is corrupted' % (dep, req_ver))
  261.  
  262. info('downloading', '%s version %s' % (dep, install_ver))
  263. try:
  264. proc = Popen(['npm', 'install', '%s@%s' % (dep, install_ver)])
  265. proc.wait()
  266.  
  267. except FileNotFoundError:
  268. fatal('npm not installed')
  269. info('installed', '%s version %s' % (dep, install_ver))
  270.  
  271.  
  272.  
  273.  
  274.  
  275.  
  276. def main():
  277. """Main function to read the config file, and execute each target.
  278. """
  279. (build_file, [cmd, *args]) = parse_args()
  280.  
  281. if not path.isfile(build_file):
  282. fatal('%s: no such file or directory' % build_file)
  283.  
  284. if cmd not in VALID_COMMANDS:
  285. fatal('no such command: %s' % cmd, do_exit=False)
  286. suggestions = get_close_matches(cmd, VALID_COMMANDS)
  287. if suggestions:
  288. print('\n Did you mean \'%s\'\n' % suggestions[0])
  289. exit(1)
  290.  
  291. config = cp.ConfigParser()
  292. config.read(build_file)
  293.  
  294. if 'package' not in config:
  295. fatal('%s: missing \'package\' section' % build_file)
  296. if 'dependencies' not in config:
  297. fatal('%s: missing \'dependencies\' section' % build_file)
  298. if 'name' not in config['package']:
  299. fatal('%s: missing \'name\' value in the \'package\' section'
  300. % build_file)
  301.  
  302. if cmd == 'build':
  303. install_deps(config.items('dependencies'))
  304.  
  305.  
  306. if __name__ == '__main__':
  307. main()
  308.  
  309. # -*-Python-*-
  310. # End of [atsmake]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement