def sk_layout(args): """Manipulate the tab order in resulting htmls by changing the order of keys of 'analysis' dict in scikick.yml. """ skconf = ScikickConfig(need_pages=True) tabs = get_tabs(skconf) # modify the layout of a submenu if args.submenu is not None: if args.submenu[0] not in tabs: reterr(f"sk: Error: No menu named \"{args.submenu[0]}\"") if len(args.order) != 0: skconf = rearrange_submenus(args.submenu[0], args.order, skconf, tabs) yaml_dump(skconf.config) tabs = get_tabs(skconf) submenu_contents = tabs[args.submenu[0]] submenu_text = list( map(lambda r: os.path.basename(r), submenu_contents)) for i in range(len(submenu_text)): print(f"{i + 1}: {submenu_text[i]}") else: if len(args.order) != 0: skconf = rearrange_tabs(args.order, skconf, tabs) yaml_dump(skconf.config) # print layout tabs = get_tabs(skconf) for i in range(len(tabs.keys())): print(f"{i + 1}: {list(tabs.keys())[i]}")
def site(args): """Adds the custom link to the 'More' tab""" ymli = yaml_in() if "site" not in ymli.keys(): ymli["site"] = dict() # print the links if args.get: for k in ymli["site"].keys(): print(f"{k} => {ymli['site'][k]}") return if args.delete and args.name is None: reterr("sk: Error: '--name' has to be specified for deletion") if args.delete: # handle deletion del_ret = ymli["site"].pop(args.name[0], None) if del_ret is not None: print(f"sk: Removed link named '{args.name[0]}'") else: print(f"sk: Link '{args.name[0]}' not found") yaml_dump(ymli) return if (args.name is None) or (args.link is None): reterr("sk: Error: both '--name' and '--link' have to be specified") ymli["site"][args.name[0]] = args.link[0] print(f"sk: Added link {args.link[0]} under {args.name[0]}") yaml_dump(ymli)
def run_sk_dryrun(snakefile=get_sk_snakefile(), workdir=os.getcwd()): # get snakemake --dryrun output with --reason # TODO - use python API status = subprocess.run( ["snakemake", "--snakefile", snakefile, \ "--directory", workdir, \ "--dryrun", "--reason"], \ stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) if status.returncode != 0: warn("sk: There was an error in snakemake --dry-run, see below") reterr(status.stdout.decode("utf-8")) stdout = status.stdout.decode("utf-8").split("\n") # Filter output for job/reason outputs only stdout = list( filter( lambda l: re.match(job_pattern, l) is not None or re.match( reason_pattern, l) is not None, stdout)) # Prevent status from return is something goes wrong #assert status.returncode == 0, "snakemake dryrun failed" # get "Job..." and "Reason:..." line pairs jobs = list(map(lambda i: stdout[i * 2], range(0, int(len(stdout) / 2)))) reasons = list( map(lambda i: stdout[i * 2 + 1], range(0, int(len(stdout) / 2)))) return jobs, reasons
def report_dir(self): """Get the value of 'reportdir' from scikick.yml""" if self.config['reportdir'] is None: reterr( "sk: Error: Report directory (reportdir) has not been set in scikick.yml" ) # Eventually it may be safe to set this value dynamically #warn("sk: Warning: Setting reportdir to 'report' and writing to scikick.yml") #self.config['reportdir'] = "report" # yaml_dump may be unsafe if self.config has been changed elsewhere # A direct modification of reportdir would be better # yaml_dump(self.config) return os.path.normpath(self.config['reportdir'])
def new_tab_order(args_order, tab_keys): """Get the new order of tabs based on the user input. Expands the user provided order to the full explicit index order. Returns a list of indices, which would be used to reorder the 'analysis' keys in scikick.yml args_order -- order provided by the user (list of indices as strings) tabs -- list of tab names """ # in case 9 or less tabs, the the order can be without spaces if len(args_order) == 1: order = list(map(lambda x: int(x) - 1, str(args_order[0]))) else: order = list(map(lambda x: x - 1, args_order)) if len(set(order)) == len(order) and \ len(order) <= len(tab_keys) and \ min(order) >= 0 and max(order) < len(tab_keys): # fill the rest of idxs if not all provided for i in range(len(tab_keys)): if i not in order: order.append(i) return order else: reterr("sk: Error: Inputs must be a unique list of tab indices")
def yaml_in(ymlpath='scikick.yml', need_pages=False): """Read scikick.yml. Returns an ordereddict. need_pages -- logical, whether to error if analysis is empty """ #Exit with an error message if scikick.yml is not found if not os.path.isfile(ymlpath): reterr(f"sk: Error: {ymlpath} not found," + \ "to get a template, run\nsk: sk init") ymli = yaml.YAML() ymli = ymli.load(open(ymlpath, "r")) if ymli is None: warn("sk: Warning: scikick.yml is empty") ymli = dict() ## Checks that will be modified for this read-in only # make sure that mandatory fields are present if "analysis" not in ymli.keys(): if need_pages: reterr("sk: Error: no pages have been added to scikick.yml, " + \ "this can be done with\nsk: sk add my.rmd") else: warn("sk: Warning: scikick.yml is missing analysis field") ymli["analysis"] = ordereddict() else: if ymli["analysis"] is None: ymli["analysis"] = ordereddict() if len(ymli["analysis"]) == 0: if need_pages: reterr("sk: Error: no pages have been added to scikick.yml, " + \ "this can be done with\nsk: sk add my.rmd") else: ymli["analysis"] = ordereddict() if "reportdir" not in ymli.keys(): warn("sk: Warning: scikick.yml is missing reportdir field") ymli["reportdir"] = "" return ymli
def sk_move_check(src, dest): """Perform checks on src and dest and quit if bad arguments are given src -- list of files to move des -- list containing the file/dir to move to """ config = yaml_in() for s in src: if not os.path.exists(s): reterr(f"sk: Error: file or directory {s} doesn't exist") if len(src) > 1: if not os.path.isdir(dest[0]): reterr("sk: Error: moving multiple files to a single one") elif len(src) == 1 and (not os.path.isdir(dest[0])): old_ext = os.path.splitext(src[0])[1] new_ext = os.path.splitext(dest[0])[1] if old_ext.lower() != new_ext.lower(): warn("sk: Warning: changing file extension") if (src[0] in config["analysis"].keys()) and \ (new_ext.lower() not in map(str.lower, supported_extensions)): reterr( f"sk: Error: only extensions {', '.join(supported_extensions)} are supported ({new_ext} given)" )
def add(files, deps=list(), force=False, copy_deps=None): """ Add files and dependencies to them files -- page file list deps -- dependency file list force -- bool whether to add additional index files copy_deps -- file to copy dependencies from """ if deps is None: deps = list() ymli = yaml_in() if copy_deps is not None: # copy_deps(src,dest) copy_deps = copy_deps[0] if copy_deps not in ymli["analysis"]: reterr(f"sk: Error: file {copy_deps} is not included") deps2copy = ymli["analysis"][copy_deps] if not ((deps2copy is None) or (len(deps2copy) == 0)): deps = list(set(deps + deps2copy)) warn(f"sk: Copying {copy_deps} dependencies") else: warn(f"sk: Warning: {copy_deps} has no dependencies") # add files for fname in files: # Add script # Should the script be added? add_fname = add_check(fname, ymli, force, deps) if add_fname: # add near scripts in the same directory if len(ymli["analysis"].keys()) > 0: commpath = os.path.commonpath(list(ymli["analysis"].keys())) else: commpath = "" tab_name = os.path.dirname(rm_commdir(fname, commpath)) all_tabs = list( map(lambda f: os.path.dirname(rm_commdir(f, commpath)), ymli["analysis"].keys())) tab_matches = list( filter(lambda i: all_tabs[i] == tab_name, range(len(all_tabs)))) if (tab_name != "") and (len(tab_matches) != 0): ymli["analysis"].insert(tab_matches[-1] + 1, fname, []) else: ymli['analysis'][fname] = None warn(f"sk: Added {fname}") # Add dependencies for dep in deps: # Should the dep be added? add_dep = add_dep_checks(fname, ymli, force, dep) if add_dep: if ymli['analysis'][fname] is None: ymli['analysis'][fname] = [] ymli['analysis'][fname].append(dep) warn(f"sk: Added dependency {dep} to {fname}") if dep in ymli["analysis"].keys(): warn( f"sk: {fname} will be executed after any executions of {dep}" ) else: warn( f"sk: {fname} will be executed after any modifications to {dep}" ) yaml_dump(ymli)
def add_check(fname, ymli, force, deps): """Performs a check if fname can be added to scikick.yml as a key""" pages = ymli["analysis"].keys() if fname in pages: warn(f"sk: Found existing entry for {fname}") return False # filenames cannot currently have wildcard symbols if True in [i in fname for i in wildcard_symbols]: warn( f"sk: Error: Filename ({fname}) cannot have wildcard symbols ({' '.join(wildcard_symbols)})" ) return False # check if the file extension is supported fext = os.path.splitext(fname)[-1] if fext.lower() == ".ipynb": warn( "sk: Warning: .ipynb use in Scikick is experimental and requires installation of jupyter" ) f_exe_support = fext.lower() in [x.lower() for x in supported_extensions] if not f_exe_support: extension_list_str = ', '.join(supported_extensions) warn("sk: Error: Only %s files can be added as pages (%s)" % \ (extension_list_str, fname)) return False # error if the directory doesn't exist dirname = os.path.dirname(fname) if dirname != "": if not os.path.isdir(dirname): reterr(f"sk: Error: Directory {dirname} does not exist.") # create the file if it doesn't exist if not os.path.isfile(fname): warn(f"sk: Warning: File {fname} doesn't exist") warn(f"sk: Creating new file {fname}") open(fname, "a").close() if fname not in pages: # Check for other files with same basename (and therefore same md output file) fname_base = os.path.splitext(fname)[0] page_basenames = map(lambda x: os.path.splitext(x)[0], pages) page_basename_exists = fname_base in page_basenames if page_basename_exists: warn( f"sk: Error: Page {fname_base} is already to be compiled from another file." ) return False # check for "index.Rmd"s index_list = get_indexes(ymli) if os.path.splitext(os.path.basename(fname))[0] == "index": if len(index_list) == 0: warn( f"sk: An index file {fname} has been added and will be used as the homepage" ) # touch the added index file to ensure execution os.utime(fname, None) elif len(index_list) == 1: if not force: reterr(f"sk: Error: An index file {index_list[0]} already exists\n" + \ "sk: Error: An additional one can be added, but neither will be used as a homepage\n" + \ f"sk: Error: To persist, use 'sk add --force {fname}'") else: warn( f"sk: Warning: A redundant index file has been added\n" + "sk: Warning: Neither of the added index files will be used as a homepage" ) os.utime( os.path.join(get_sk_exe_dir(), "workflow", "notebook_rules", "index.Rmd"), None) elif len(index_list) > 1: warn( f"sk: Warning: A redundant index file has been added\n" + "sk: Warning: Neither of the added index files will be used as a homepage" ) return True
def analysis(self): """Get 'analysis:' dict from scikick.yml""" if self.config['analysis'] is not None: return self.config['analysis'] reterr("No analysis field found in scikick config") return None