def grep(cfg: Dict[str, str], pattern: str, case_blind: bool = False) -> NoReturn: """ Searches for the specified regular expression in every notebook within the current course, printing the colorized matches to standard output. If PAGER is set, the matches will be piped through the pager. Note that this function does NOT use grep(1). It implements the regular expression matching and colorization entirely within Python. :param cfg: The config. :param pattern: The regular expression (a string, not a compiled pattern) to find :param case_blind: Whether or not to use case-blind matching :return: Nothing """ def grep_one(path: str, r: Pattern, out: TextIO) -> NoReturn: home = os.environ['HOME'] if home: printable_path = os.path.join('~', path[len(home) + 1:]) else: printable_path = path matches = [] with open(path) as f: for line in f.readlines(): m = r.search(line) if not m: continue # If there's a pager, colorize the match. if cfg.get('PAGER'): s = m.start() e = m.end() matches.append(line[:s] + colored(line[s:e], 'red', attrs=['bold']) + line[e:]) else: matches.append(line) if matches: out.write(f'\n\n=== {printable_path}\n\n') out.write(''.join(matches)) r = None try: flags = 0 if not case_blind else re.IGNORECASE r = re.compile(pattern, flags=flags) except Exception as e: die(f'Cannot compile regular expression "{pattern}": {e}') check_config(cfg, 'COURSE_NAME', 'COURSE_REPO') with pager(cfg) as out: for nb in bdc.bdc_get_notebook_paths(build_file_path(cfg)): grep_one(nb, r, out)
def gendbc( source_dir: str, encoding: str, dbc_path: str, dbc_folder: str, flatten: bool, verbose: bool, debugging: bool = False, ) -> NoReturn: """ Generate a DBC from all the notebooks under a specific source directory. :param source_dir: the directory to scan for source notebooks :param encoding: the encoding to use when opening the notebooks :param dbc_path: the path to the DBC file to create or overwrite :param dbc_folder: top-level DBC folder to create, if any. None means just use the partial paths. :param flatten: If True, flatten all the files into one folder in the zip file. If dbc_folder is specified, all files will go in that folder. Otherwise, they'll be at the top of the DBC. :param verbose: Whether or not to emit verbose messages. :param debugging: Whether or not to emit debug messages. :return: nothing """ params = Config( debug=debugging, verbose=verbose, encoding=encoding, dbc_folder=dbc_folder, flatten=flatten, show_stack=True, source_dir=source_dir, dbc=dbc_path, ) if params.dbc_folder and ("/" in params.dbc_folder): raise UsageError( f'The specified DBC top folder, "{params.dbc_folder}", must be ' + "a simple directory name, not a path.") notebook_paths = _find_notebooks(params.source_dir, params.encoding) if len(notebook_paths) == 0: die(f'No source notebooks found under "{params.source_dir}".') notebooks = [ parse_source_notebook(i, params.encoding, debugging) for i in notebook_paths ] _write_dbc(notebooks, params)
def build_local(cfg: Dict[str, str]) -> str: """ Build a course without uploading the results. :param cfg: the loaded config :return: the path to the build file, for convenience """ check_config(cfg, 'COURSE_NAME', 'COURSE_REPO') course_name = cfg['COURSE_NAME'] build_file = build_file_path(cfg) if not os.path.exists(build_file): die('Build file "{}" does not exist.'.format(build_file)) print(f"\nBuilding {course_name} using {os.path.basename(build_file)}") bdc.bdc_build_course(build_file, dest_dir='', overwrite=True, verbose=False) return build_file
def main(): if os.environ.get('COURSE_DEBUG', 'false') == 'true': set_debug(True) try: # Load the configuration and then run it through update_config() to # ensure that course name-related settings are updated, if necessary. cfg = update_config(load_config(CONFIG_PATH, show_warnings=True)) # Update the environment, for subprocesses we need to invoke. os.environ['EDITOR'] = cfg['EDITOR'] os.environ['PAGER'] = cfg['PAGER'] # Loop over the argument list, since we need to support chaining some # commands (e.g., "course download build"). This logic emulates # what was in the original shell script version, and it's not easily # handled by Python's argparse or docopt. So, ugly as it is, we go # with manual parsing. if len(sys.argv) == 1: args = ["help"] else: args = sys.argv[1:] i = 0 while i < len(args): cmd = args[i] if cmd in ('--version', '-V'): print(VERSION) break if cmd in ('toolversions', 'tool-versions'): print_tool_versions() break if cmd in ('-n', '--name'): try: i += 1 # Changing the name of the course has to reset the # build.yaml name. del cfg['COURSE_YAML'] cfg['COURSE_NAME'] = args[i] cfg = update_config(cfg) except IndexError: die("Saw -n or --name without subsequent course name.") elif cmd in ('-f', '--build-file'): try: i += 1 cfg['COURSE_YAML'] = args[i] cfg = update_config(cfg) except IndexError: die("Saw -f or --build-file without subsequent file name.") elif cmd in ('-h', '--help', 'help', 'usage'): help(cfg) break elif cmd in ('work-on', 'workon'): try: i += 1 cfg = work_on(cfg, args[i], CONFIG_PATH) except IndexError: die('Expected course name after "work-on".') elif cmd == 'tag': git_tag(cfg) elif cmd == 'which': which(cfg) elif cmd in ('install-tools', 'installtools'): install_tools() elif cmd == 'download': download(cfg) elif cmd == 'upload': upload(cfg) elif cmd in ('upload-built', 'uploadbuilt'): upload_build(cfg) elif cmd == 'build': build_and_upload(cfg) elif cmd in ('build-local', 'buildlocal'): build_local(cfg) elif cmd == 'clean': clean(cfg) elif cmd in ('clean-source', 'cleansource'): clean_source(cfg) elif cmd in ('deploy-images', 'deployimages'): deploy_images(cfg) elif cmd == 'status': git_status(cfg) elif cmd == 'diff': git_diff(cfg) elif cmd == 'difftool': git_difftool(cfg) elif cmd == 'home': browse_directory(cfg, cfg['COURSE_HOME'], 'home') elif cmd == 'modules': browse_directory(cfg, cfg['COURSE_MODULES'], 'modules') elif cmd == 'repo': browse_directory(cfg, cfg['COURSE_REPO'], 'repo') elif cmd == 'config': cfg = edit_config(cfg) elif cmd == 'yaml': edit_file(cfg, build_file_path(cfg), 'yaml') elif cmd == 'guide': edit_file( cfg, os.path.join(cfg['COURSE_HOME'], 'Teaching-Guide.md'), 'guide') elif cmd == ('deploy-images', 'deployimages'): deploy_images(cfg) elif cmd == 'grep': try: i += 1 pattern = args[i] if pattern == '-i': case_blind = True i += 1 pattern = args[i] else: case_blind = False grep(cfg, pattern, case_blind) except IndexError: die('Missing grep argument(s).') elif cmd == 'sed': try: i += 1 sed(cfg, args[i]) except IndexError: die('Missing sed argument.') elif cmd == 'xargs': # All the remaining arguments go to the command. try: i += 1 command = args[i] if i < len(args): i += 1 command_args = args[i:] else: command_args = [] run_command_on_notebooks(cfg, command, command_args) break except IndexError: die('Missing command to run.') elif cmd == 'set': try: i += 1 setting = args[i] fields = setting.split('=') if len(fields) != 2: die('Argument to "set" must be of the form CONF=VAL.') key, value = fields value = value.replace('"', '') cfg = configure(cfg, CONFIG_PATH, key, value) except IndexError: die('Missing CONF=VAL argument to "set".') elif cmd == "showconfig": hdr = "Current configuration" print('-' * len(hdr)) print(hdr) print('-' * len(hdr)) for key in sorted(cfg.keys()): print(f'{key}="{cfg[key]}"') else: die(f'"{cmd}" is not a valid "course" subcommand.') i += 1 except CourseError as e: error(str(e)) except bdc.BDCError as e: error(str(e)) except KeyboardInterrupt: error('\n*** Interrupted.')
def main(): if os.environ.get("COURSE_DEBUG", "false") == "true": set_debug(True) try: # Load the configuration and then run it through update_config() to # ensure that course name-related settings are updated, if necessary. cfg = update_config(load_config(CONFIG_PATH, show_warnings=True)) # Update the environment, for subprocesses we need to invoke. os.environ["EDITOR"] = cfg["EDITOR"] os.environ["PAGER"] = cfg["PAGER"] # Loop over the argument list, since we need to support chaining some # commands (e.g., "course download build"). This logic emulates # what was in the original shell script version, and it's not easily # handled by Python's argparse or docopt. So, ugly as it is, we go # with manual parsing. if len(sys.argv) == 1: args = ["help"] else: args = sys.argv[1:] i = 0 while i < len(args): cmd = args[i] if cmd in ("--version", "-V"): print(VERSION) break if cmd in ("toolversions", "tool-versions"): print_tool_versions() break if cmd in ("-n", "--name"): try: i += 1 # Changing the name of the course has to reset the # build.yaml name. del cfg["COURSE_YAML"] cfg["COURSE_NAME"] = args[i] cfg = update_config(cfg) except IndexError: die("Saw -n or --name without subsequent course name.") elif cmd in ("-f", "--build-file"): try: i += 1 cfg["COURSE_YAML"] = args[i] cfg = update_config(cfg) except IndexError: die("Saw -f or --build-file without subsequent file name.") elif cmd in ("-h", "--help", "help", "usage"): help(cfg) break elif cmd in ("work-on", "workon"): try: i += 1 cfg = work_on(cfg, args[i], CONFIG_PATH) except IndexError: die('Expected course name after "work-on".') elif cmd == "tag": git_tag(cfg) elif cmd == "which": which(cfg) elif cmd in ("install-tools", "installtools"): install_tools() elif cmd == "download": download(cfg) elif cmd == "upload": upload(cfg) elif cmd in ("upload-built", "uploadbuilt"): upload_build(cfg) elif cmd == "build": build_and_upload(cfg) elif cmd in ("build-local", "buildlocal"): build_local(cfg) elif cmd == "clean": clean(cfg) elif cmd in ("clean-source", "cleansource"): clean_source(cfg) elif cmd in ("deploy-images", "deployimages"): deploy_images(cfg) elif cmd == "status": git_status(cfg) elif cmd == "diff": git_diff(cfg) elif cmd == "difftool": git_difftool(cfg) elif cmd == "home": browse_directory(cfg, cfg["COURSE_HOME"], "home") elif cmd == "modules": browse_directory(cfg, cfg["COURSE_MODULES"], "modules") elif cmd == "repo": browse_directory(cfg, cfg["COURSE_REPO"], "repo") elif cmd == "config": cfg = edit_config(cfg) elif cmd == "yaml": edit_file(cfg, build_file_path(cfg), "yaml") elif cmd == "guide": edit_file( cfg, os.path.join(cfg["COURSE_HOME"], "Teaching-Guide.md"), "guide") elif cmd == ("deploy-images", "deployimages"): deploy_images(cfg) elif cmd == "grep": try: i += 1 pattern = args[i] if pattern == "-i": case_blind = True i += 1 pattern = args[i] else: case_blind = False grep(cfg, pattern, case_blind) except IndexError: die("Missing grep argument(s).") elif cmd == "sed": try: i += 1 sed(cfg, args[i]) except IndexError: die("Missing sed argument.") elif cmd == "xargs": # All the remaining arguments go to the command. try: i += 1 command = args[i] if i < len(args): i += 1 command_args = args[i:] else: command_args = [] run_command_on_notebooks(cfg, command, command_args) break except IndexError: die("Missing command to run.") elif cmd == "set": try: i += 1 setting = args[i] fields = setting.split("=") if len(fields) != 2: die('Argument to "set" must be of the form CONF=VAL.') key, value = fields value = value.replace('"', "") cfg = configure(cfg, CONFIG_PATH, key, value) except IndexError: die('Missing CONF=VAL argument to "set".') elif cmd == "showconfig": hdr = "Current configuration" print("-" * len(hdr)) print(hdr) print("-" * len(hdr)) for key in sorted(cfg.keys()): print(f'{key}="{cfg[key]}"') else: die(f'"{cmd}" is not a valid "course" subcommand.') i += 1 except CourseError as e: error(str(e)) except bdc.BDCError as e: error(str(e)) except KeyboardInterrupt: error("\n*** Interrupted.")