Source code for rse.client

#!/usr/bin/env python

"""

Copyright (C) 2020 Vanessa Sochat.

This Source Code Form is subject to the terms of the
Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""

from rse.logger import RSE_LOG_LEVEL, RSE_LOG_LEVELS
from rse.defaults import RSE_CONFIG_FILE
import rse
import argparse
import sys
import logging
import os


[docs]def get_parser(): parser = argparse.ArgumentParser( description="Research software engineering software inspector." ) parser.add_argument( "--version", dest="version", help="suppress additional output.", default=False, action="store_true", ) parser.add_argument( "--log_level", dest="log_level", choices=RSE_LOG_LEVELS, default=RSE_LOG_LEVEL, help="Customize logging level for rse inspector.", ) # Configuration file parser.add_argument( "--config_file", dest="config_file", default=RSE_CONFIG_FILE, help="Path to rse.ini configuration file.", ) description = "actions for rse" subparsers = parser.add_subparsers( help="rse actions", title="actions", description=description, dest="command", ) # print version and exit subparsers.add_parser("version", help="show software version") # Annotate criteria or taxonomy annotate = subparsers.add_parser( "annotate", help="Annotate a database with criteria or taxonomy" ) annotate.add_argument( "type", help="Type to annotate (taxonomy or criteria)", nargs=1, choices=["taxonomy", "criteria"], ) annotate.add_argument( "-u", "--username", dest="username", default=None, help="GitHub username (must be provided if not available with git config)", ) annotate.add_argument( "-r", "--repo", dest="repo", default=None, help="Specify a particular repository name to annotate.", ) annotate.add_argument( "-f", "--file", dest="file", default=None, help="Read annotation from file (e.g., markdown list, GitHub issue template).", ) annotate.add_argument( "--all", "-a", dest="all_repos", help="Annotate all repos, even those that have already been seen (defaults to show only those unseen).", default=False, action="store_true", ) # Generate a key for the interface generate = subparsers.add_parser( "generate-key", help="generate a key for rse start, should be exported to RSE_SERVER_KEY.", ) # Init init = subparsers.add_parser( "init", help="Add an rse.ini to the present working directory." ) init.add_argument( "path", help="Path to generate rse.ini file", nargs="?", default=".", ) # Config config = subparsers.add_parser( "config", help="Update an rse.ini configuration file." ) # Clear clear = subparsers.add_parser("clear", help="Remove software from the database.") clear.add_argument("target", nargs="?") clear.add_argument( "--force", dest="force", help="Don't ask for confirmation for delete (for headless).", default=False, action="store_true", ) # Exists exists = subparsers.add_parser( "exists", help="Determine if an entry exists in the database." ) # Export export = subparsers.add_parser( "export", help="Export repository names, metadata, or static files." ) export.add_argument( "--type", dest="export_type", help="Type to export (defaults to repos.txt list)", default="repos-txt", choices=["repos-txt", "static-web"], ) export.add_argument( "--force", dest="force", help="Don't ask for confirmation to overwrite existing file(s).", default=False, action="store_true", ) export.add_argument( "path", help="Fileame to export repos to (default repos.txt)", default="repos.txt", ) # Metrics summary = subparsers.add_parser( "summary", help="View summary metrics for all repositories." ) summary.add_argument( "--type", dest="metric_type", help="Metric type to view.", default="summary", choices=["criteria", "taxonomy", "users"], ) summary.add_argument( "repo", help="Filter down to one repository.", default=None, nargs="?" ) analyze = subparsers.add_parser( "analyze", help="View metrics for a specific repository." ) analyze.add_argument( "repo", help="Software repository to show", default=None, ) analyze.add_argument( "--ct", "--cthresh", dest="cthresh", help="Criteria threshold (between 0 and 1)", default=0.5, type=percentage_type_asint, ) analyze.add_argument( "--tt", "--tthresh", dest="tthresh", help="Minimum taxonomy votes to count category in response", default=1, type=positive_int_type, ) # Update update = subparsers.add_parser( "update", help="Update one or more software entries." ) update.add_argument( "-p", "--path", dest="path", default=None, help="Path to single folder or set of folders to update.", ) update.add_argument( "--force", dest="force", help="If a repository is not present, add it.", default=False, action="store_true", ) update.add_argument( "--rewrite", dest="rewrite", help="If data exists, don't update but rewrite.", default=False, action="store_true", ) # List topics (global) or matching pattern topics = subparsers.add_parser( "topics", help="List software topics (GitHub support only)" ) topics.add_argument( "--pattern", help="filter topics to a particular pattern", type=str, default=None, ) topics.add_argument( "--search", help="find repositories based on a list of topics", type=str, nargs="*", ) # List repos and print to terminal ls = subparsers.add_parser("ls", help="List software") ls.add_argument( "parser", help="list one or more parsers or specific software.", nargs="*" ) # Search for software search = subparsers.add_parser( "search", help="Search for a piece of research software", ) search.add_argument("query", nargs="*") search.add_argument("--taxonomy", nargs="*") search.add_argument("--criteria", nargs="*") # Scrape for new repos scrape = subparsers.add_parser( "scrape", help="Add new software repositories from a resource.", ) scrape.add_argument("scraper_name", nargs=1) scrape.add_argument("query", nargs="?") scrape.add_argument( "--dry-run", dest="dry_run", help="Dump scrape output to the terminal, but don't create new repos.", default=False, action="store_true", ) scrape.add_argument( "--delay", dest="delay", help="Number of seconds (float) to delay, default 0.", type=float_type, default=0.0, ) # Shell subparsers.add_parser( "shell", help="start an interactive shell for an encyclopedia" ) # Start the rse dashboard start = subparsers.add_parser( "start", help="start an interface to browse software (requires Flask)" ) for command in [start, export]: command.add_argument( "--port", dest="port", default=5000, type=int, help="select port to run dashboard on (defaults to 5000)", ) command.add_argument( "--host", dest="host", default="127.0.0.1", type=str, help="the hostname to run for the server (defaults to 127.0.0.1)", ) command.add_argument( "--debug", dest="debug", help="run server in debug mode (defaults to False)", default=False, action="store_true", ) command.add_argument( "--disable-annotate", dest="disable_annotate", help="disable annotation button (defaults to False)", default=False, action="store_true", ) # Label a repository with metadata, e.g., add a DOI. label = subparsers.add_parser( "label", help="Add a metadata value for an existing repository" ) label.add_argument("values", nargs=3) label.add_argument( "--force", dest="force", help="Overwrite existing label, if it exists.", default=False, action="store_true", ) # Print complete metadata for a specific piece of software get = subparsers.add_parser("get", help="Show metadata for software") add = subparsers.add_parser("add", help="Add a repository to the database.") # Most commands allow specification of a database backend for command in [ add, analyze, annotate, clear, config, exists, export, get, init, label, ls, search, scrape, start, summary, topics, update, ]: command.add_argument( "--database", dest="database", choices=["filesystem", "sqlite"], default=None, help="database backend to use, to override configuration.", ) for command in [exists, get]: command.add_argument("uid", help="uri within software namespace.", nargs="?") for command in [update, add]: command.add_argument("uid", help="uri within software namespace.", nargs="?") command.add_argument( "--file", dest="file", default=None, help="single line delimited file of repositories.", ) return parser
[docs]def percentage_type_asint(arg): """ensure that an input is between 0 and 1""" try: number = float(arg) except ValueError: raise argparse.ArgumentTypeError("Must be a floating point number") if number < 0 or number > 1: raise argparse.ArgumentTypeError("Argument must be between 0 and 1") return number
[docs]def float_type(arg): """ensure that an input is greater than 0 and a float""" try: number = float(arg) except ValueError: raise argparse.ArgumentTypeError("Must be a floating point number") if number < 0: raise argparse.ArgumentTypeError("Argument must be greater than 0.") return number
[docs]def positive_int_type(arg): """ensure user is providing a positive integer""" value = int(arg) if value < 0: raise argparse.ArgumentTypeError( "%s is an invalid positive integer value" % arg ) return value
[docs]def main(): """main entrypoint for rse""" parser = get_parser() def help(return_code=0): """print help, including the software version and active client and exit with return code. """ version = rse.__version__ print("\nResearch Software Engineering software inspector v%s" % version) parser.print_help() sys.exit(return_code) # If the user didn't provide any arguments, show the full help if len(sys.argv) == 1: help() # If an error occurs while parsing the arguments, the interpreter will exit with value 2 args, extra = parser.parse_known_args() # Set the logging level os.putenv("RSE_LOG_LEVEL", args.log_level) RSE_LOG_LEVEL = args.log_level logging.basicConfig(level=getattr(logging, args.log_level)) bot = logging.getLogger("rse.client") bot.setLevel(getattr(logging, args.log_level)) # Show the version and exit if args.command == "version" or args.version: print(rse.__version__) sys.exit(0) # Does the user want a shell? if args.command == "analyze": from .metrics import analyze as main if args.command == "annotate": from .annotate import main if args.command == "add": from .add import main if args.command == "clear": from .clear import main if args.command == "config": from .config import main if args.command == "exists": from .exists import main if args.command == "export": from .export import main if args.command == "generate-key": from .generate import main if args.command == "label": from .label import main if args.command == "update": from .update import main if args.command == "get": from .get import main if args.command == "init": from .init import main if args.command == "ls": from .listing import main if args.command == "summary": from .metrics import summary as main if args.command == "search": from .search import main if args.command == "scrape": from .scrape import main if args.command == "shell": from .shell import main if args.command == "start": from .start import main if args.command == "topics": from .topics import main # Pass on to the correct parser main(args=args, extra=extra)
if __name__ == "__main__": main()