def parse_binding(ctx, line_num, line): words = line.split() if not words: return cmd = words[0] if cmd not in ctx.all_cmds: errFL('warning: {}: unknown command: {}', line_num, cmd) try: when_index = words.index('when') except ValueError: keys = words[1:] whens = [] else: keys = words[1:when_index] whens= words[when_index+1:] ctx.bound_cmds.add(cmd) if not keys: return validate_keys(line_num, keys) binding = { 'command': cmd, 'key': ' '.join(keys) } if whens: validate_whens(ctx, line_num, whens) binding['when'] = ' '.join(whens) ctx.bindings.append(binding)
def load(target_path, ext=None, **kwargs): ''' Select an appropriate loader based on the file extension, or `ext` if specified. If a loader is found, then `open_dep` is called with the default `open_dep_kwargs` registered by `add_loader`, except updated by any values with matching keys in `kwargs`. The remaining `kwargs` are passed to the loader function registered by `add_loader`. Thus, keyword arguments passed to `load` get divvied up between `open_dep` and the custom load function. If no loader is found, raise an error. Muck's static analysis looks specifically for this function to infer dependencies; `target_path` must be a string literal. ''' if ext is None: ext = path_ext(target_path) elif not isinstance(ext, str): raise TypeError(ext) try: load_fn, std_open_args = _loaders[ext] except KeyError: errFL('ERROR: No loader found for target: {!r}', target_path) errFL('NOTE: extension: {!r}', ext) raise open_args = std_open_args.copy() # transfer all matching kwargs to open_args. for k in _open_deps_parameters: try: v = kwargs[k] except KeyError: continue open_args[k] = v del kwargs[k] # only pass this arg to open_deps; del is safe because kwargs has local lifetime. file = open_dep(target_path, **open_args) return load_fn(file, **kwargs)
def muck_patch(ctx, args): if not len(args) in (1, 2): failF('''\ muck patch error: patch command takes one or two arguments. usage: muck patch [original_target] [target] creates a new target by copying either the source or product of the original to _build/[target], and then creates an empty [target].pat. muck patch [target.pat] update the patch file with the diff of the previously specified original and target. ''') if len(args) == 2: # create new patch. orig_target_path, target_path = args if orig_target_path.endswith('.pat'): errFL('muck patch error: original should not be a patch file: {}', orig_target_path) if target_path.endswith('.pat'): errFL('muck patch error: {} {}: target should not be a patch file: {}', target_path) patch_path = target_path + '.pat' if path_exists(patch_path): failF('muck patch error: {}: patch already exists.', patch_path) update_dependency(ctx, orig_target_path, dependent=None) orig_path = actual_path_for_target(orig_target_path) prod_path = product_path_for_target(target_path) if path_exists(prod_path): errFL('muck patch note: product already exists: {}', prod_path) else: errFL('muck patch note: copying original to product: {} -> {}', orig_path, prod_path) copy_file(orig_path, prod_path) else: # update existing patch. patch_path = args[0] if path_ext(patch_path) != '.pat': failF('muck patch error: argument does not specify a .pat file: {!r}', patch_path) deps = pat_dependencies(patch_path, open(patch_path), {}) orig_target_path = deps[0] update_dependency(ctx, orig_target_path, dependent=None) orig_path = actual_path_for_target(orig_target_path) target_path = path_stem(patch_path) prod_path = product_path_for_target(target_path) # update patch (both cases). patch_path_tmp = patch_path + tmp_ext cmd = ['pat', 'diff', orig_path, prod_path] errFL('muck patch note: diffing: `{}`', ' '.join(shlex.quote(w) for w in cmd)) with open(patch_path_tmp, 'wb') as f: code = runC(cmd, out=f) move_file(patch_path_tmp, patch_path, overwrite=True) if len(args) == 1: # updated existing patch. # need to remove or update the target record to avoid the 'did you mean to patch?' safeguard. # for now, just delete it to be safe; this makes the target look stale. try: ctx.db.delete_record(target_path=target_path) except KeyError: pass
def muck_clean(ctx, args): ''' `muck clean` command. ''' if not args: failF('muck clean error: clean command takes specific target arguments; use clean-all to remove all products.') for target in args: if not ctx.db.contains_record(target_path=target): errFL('muck clean note: {}: skipping unknown target.', target) continue prod_path = product_path_for_target(target) remove_file_if_exists(prod_path) ctx.db.delete_record(target_path=target)
def open_dep(target_path, binary=False, buffering=-1, encoding=None, errors=None, newline=None): ''' Open a dependency for reading. Muck's static analysis looks specifically for this function to infer dependencies; `target_path` must be a string literal. ''' path = actual_path_for_target(target_path) try: return open(path, mode=('rb' if binary else 'r'), buffering=buffering, encoding=encoding, errors=errors, newline=newline) except FileNotFoundError: errFL('muck.open_dep cannot open path: {}', path) if path != target_path: errFL('note: nor does a file exist at source path: {}', target_path) raise
def fetch(url, expected_status_code=200, headers={}, timeout=4, delay=0, delay_range=0): "Fetch the data at `url` and save it to a path in the '_fetch' directory derived from the URL." path = path_join('_fetch', path_for_url(url)) if not path_exists(path): errFL('fetch: {}', url) r = _fetch(url, timeout, headers, expected_status_code) make_dirs(path_dir(path)) with open(path, 'wb') as f: f.write(r.content) sleep_min = delay - delay_range * 0.5 sleep_max = delay + delay_range * 0.5 sleep_time = random.uniform(sleep_min, sleep_max) if sleep_time > 0: time.sleep(sleep_time) return path
#!/usr/bin/env python3 # Dedicated to the public domain under CC0: https://creativecommons.org/publicdomain/zero/1.0/. from sys import argv, stdin, stdout from json import JSONDecoder, dump from pithy.io import errFL if len(argv) > 2: exit('requires 1 or no arguments (defaults to std-in)') if len(argv) == 2: f = open(argv[1]) else: f = stdin s = f.read() o, end_index = JSONDecoder(strict=False).raw_decode(s) dump(o, stdout, sort_keys=True, indent=2) stdout.write('\n') if end_index < len(s): tail = '…' if end_index + 64 < len(s) else '' errFL('extraneous input beginning at offset {}:\n{}{}\n', end_index, s[end_index:end_index + 64].encode('utf8'), tail)
def failF(path, fmt, *items): errF('muck error: {}: ', path) errFL(fmt, *items) exit(1)
def warnF(path, fmt, *items): errF('muck WARNING: {}: ', path) errFL(fmt, *items)
def noteF(path, fmt, *items): errF('muck note: {}: ', path) errFL(fmt, *items)
def dbgF(path, fmt, *items): errFL('muck dbg: {}: ' + fmt, path, *items)