def run_evaluation(): global report_exact global detail_report global check_train global nopt parser = argparse.ArgumentParser(description="Classifier") parser.add_argument('--cat', help='category') parser.add_argument('--extra', help='extra category') parser.add_argument('--app', help='only for app', default='ALL') parser.add_argument('--screen', help='only for screen', default='ALL') parser.add_argument('--term', help='only for term', default='ALL') parser.add_argument('--quiet', help='no detail', default=False, action='store_const', const=True) parser.add_argument('--reallyquiet', help='no any detail', default=False, action='store_const', const=True) parser.add_argument('--nopt', help='ignore pt cache', default=False, action='store_const', const=True) parser.add_argument('--show', help='show feature', default=False, action='store_const', const=True) parser.add_argument('--guispath', help='data path', default=DATADIR) parser.add_argument('--tagspath', help='tags path', default="../etc/tags.txt") parser.add_argument('--extrapath', help='extra data path', default="../guis-extra/") parser.add_argument('--extrascr', help='extra screens', default=config.extra_screens) parser.add_argument('--seed', help='random seed', default=0, type=int) parser.add_argument('--apps', help='only use apps from arg') args = parser.parse_args() if args.cat: args.guispath = "../guis-%s/" % args.cat args.tagspath = "../etc-%s/" % args.cat args.apps = "../etc-%s/applist.txt" % args.cat if args.extra: args.extrapath = "../guis-%s/" % args.extra taginfo.load(args.tagspath) if args.reallyquiet: report_exact = False detail_report = False check_train = False if args.quiet: report_exact = False if args.nopt: nopt = True logger.info("Loading pts") (datapts, apps, tags, cnt_by_tag) = load_datapts(args.guispath, extrapath=args.extrapath, extrascr=args.extrascr) if args.show: for pt in datapts: if args.app and pt['app'] != args.app: continue if args.screen and pt['scr'] != args.screen: continue print(pt['file']) print('tree:', re.sub(r'\s+', ' ', pt['tree'])) if args.apps: select_apps = open(args.apps).read().strip().split(',') select_pts = [] #select_tags = [] for i in range(len(datapts)): if datapts[i]['app'] in select_apps or datapts[i]['extra']: select_pts.append(datapts[i]) apps = select_apps datapts = select_pts if args.app.startswith('SEL'): random.seed(args.seed) app_count = int(args.app[3:]) select_apps = [] all_apps = apps for i in range(app_count): selected = random.choice(all_apps) select_apps.append(selected) all_apps.remove(selected) print("selected: %s" % select_apps) select_pts = [] #select_tags = [] for i in range(len(datapts)): if datapts[i]['app'] in select_apps or datapts[i]['extra']: select_pts.append(datapts[i]) apps = select_apps datapts = select_pts #tags = select_tags args.app = 'ALL' logger.info("Evaluating, pt cnt: %d" % len(datapts)) if args.app != 'ALL' and args.screen != 'ALL': return evaluate_single(datapts, args.app, args.screen, args.term) test_err = 0 test_correct = 0 tot_score = 0 score_count = 0 errs = {} # type: Dict[str, int] err_by_tag = {} # type: Dict[str, int] case_by_app = {} # type: Dict[str, int] err_detail = {} # type: Dict[str, int] tot_webview = 0 err_webview = 0 if args.app != 'ALL': score_count = 1 (tot_score, test_err, test_correct, errs, err_by_tag, case_by_app, err_detail, err_webview, tot_webview) = evaluate( datapts, apps, tags, app=args.app, evalscreen=args.screen) else: pool = multiprocessing.Pool(processes=thread_count) for (xtot_score, xtest_err, xtest_correct, xerrs, xerr_by_tag, xcase_by_app, xerr_detail, xerr_webview, xtot_webview) in pool.map( functools.partial(evaluate, datapts, apps, tags, evalscreen=args.screen), apps): if xtot_score is not None: score_count += 1 tot_score += xtot_score test_err += xtest_err test_correct += xtest_correct tot_webview += xtot_webview merge_cnt(errs, xerrs) merge_cnt(err_by_tag, xerr_by_tag) merge_cnt(case_by_app, xcase_by_app) merge_list2(err_detail, xerr_detail) err_webview += xerr_webview pool.close() if detail_report: print("errs:") for app in apps: if app in errs: if app not in case_by_app: case_by_app[app] = 1 print(" %12s: %3d / %3d %.3f" % (app, errs[app], case_by_app[app], 1.0 * errs[app] / case_by_app[app])) if app in err_detail: for tag in tags: if tag in err_detail[app]: print(" tag %9s: %3d %s" % (tag, len(err_detail[app][tag]), ' '.join(err_detail[app][tag]))) print("err by tag:") for tag in (tags if args.screen == 'ALL' else [args.screen]): if tag not in err_by_tag: err_by_tag[tag] = 0 print(" %10s: %3d / %3d %.3f" % ( tag, err_by_tag[tag], cnt_by_tag[tag], 1.0 * err_by_tag[tag] / cnt_by_tag[tag])) print("test cases: %d- %d+ total: %d" % (test_err, test_correct, test_err + test_correct)) print("error of webview: %d / %d" % (err_webview, tot_webview)) print("final test: %.3f total: %d/%d" % (tot_score / score_count, test_correct, test_correct + test_err))
appdb.collect_apps("../apks/") elif dev.kind == 'web': appdb.load_urls("../etc/urls.txt") ob = observer.Observer("../err/") init_state = state.State() env = environ.Environment() tlib = testlib.collect_pieces("../tlib/") tlib.assume_reached('signin', 'login', 'main') tlib.assume_reached('welcome', 'skip sign in / sign up', 'main') ob.tlib = tlib init_env = {'dev': dev, 'ob': ob, 'state': init_state, 'env': env, 'tlib': tlib} if len(sys.argv) > 2: app = sys.argv[2] load_app(app, ob, tlib, init_env) cons = console.Console(handle_console_cmd, prompt="op> ", after_cb=update_prompt, init_env=init_env) cons.start() cons.wait() if __name__ == '__main__': if "--debug" in sys.argv: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) tags.load("../etc/tags.txt") run_console()
def main(): config, config_yaml = load_config() derive_config(config) unique_suffix = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M") # We discover the current git branch/commit # so that the deployment script can use it # to clone the same commit. commit = os.popen("""git rev-parse HEAD""").read().rstrip() assert commit assert "'" not in config['SITE_NAME'] instance_tags = tags.load() # Set the `Name` as it appears on the EC2 web UI. instance_tags.append({'Key': 'Name', 'Value': "refinery-web-" + unique_suffix}) config['tags'] = instance_tags config_uri = save_s3_config(config, unique_suffix) sys.stderr.write("Configuration saved to {}\n".format(config_uri)) # The userdata script is executed via CloudInit. # It's made by concatenating a block of parameter variables, # with the bootstrap.sh script, # and the aws.sh script. user_data_script = functions.join( "", "#!/bin/sh\n", "CONFIG_YAML=", base64.b64encode(config_yaml), "\n", "CONFIG_JSON=", base64.b64encode(json.dumps(config)), "\n", "AWS_DEFAULT_REGION=", functions.ref("AWS::Region"), "\n", "RDS_ID=", functions.ref('RDSInstance'), "\n", "RDS_ENDPOINT_ADDRESS=", functions.get_att('RDSInstance', 'Endpoint.Address'), "\n", "RDS_ENDPOINT_PORT=", functions.get_att('RDSInstance', 'Endpoint.Port'), "\n", "RDS_SUPERUSER_PASSWORD="******"\n", "RDS_ROLE=", config['RDS_ROLE'], "\n", "ADMIN=", config['ADMIN'], "\n", "DEFAULT_FROM_EMAIL=", config['DEFAULT_FROM_EMAIL'], "\n", "SERVER_EMAIL=", config['SERVER_EMAIL'], "\n", "IAM_SMTP_USER="******"\n", "S3_CONFIG_URI=", config['S3_CONFIG_URI'], "\n", "SITE_URL=", config['SITE_URL'], "\n", # May contain spaces, but can't contain "'" "SITE_NAME='", config['SITE_NAME'], "'\n", "GIT_BRANCH=", commit, "\n", "\n", open('bootstrap.sh').read(), open('aws.sh').read()) cft = core.CloudFormationTemplate(description="refinery platform.") rds_properties = { "AllocatedStorage": "5", "AvailabilityZone": config['AVAILABILITY_ZONE'], "BackupRetentionPeriod": "0", "DBInstanceClass": "db.t2.small", # todo:? "DBInstanceIdentifier": config['RDS_NAME'], "Engine": "postgres", "EngineVersion": "9.3.10", # "KmsKeyId" ? "MasterUsername": "******", "MasterUserPassword": "******", "MultiAZ": False, "Port": "5432", "PubliclyAccessible": False, "StorageType": "gp2", "Tags": instance_tags, # todo: Should be different? } if 'RDS_SNAPSHOT' in config: rds_properties['DBSnapshotIdentifier'] = config['RDS_SNAPSHOT'] cft.resources.rds_instance = core.Resource( 'RDSInstance', 'AWS::RDS::DBInstance', core.Properties(rds_properties), core.DeletionPolicy("Snapshot"), ) volume_properties = { 'AvailabilityZone': config['AVAILABILITY_ZONE'], 'Encrypted': True, 'Size': config['DATA_VOLUME_SIZE'], 'Tags': tags.load(), 'VolumeType': config['DATA_VOLUME_TYPE'], } if 'DATA_SNAPSHOT' in config: volume_properties['SnapshotId'] = config['DATA_SNAPSHOT'] # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html cft.resources.ebs = core.Resource( 'RefineryData', 'AWS::EC2::Volume', core.Properties(volume_properties), core.DeletionPolicy("Snapshot"), ) cft.resources.ec2_instance = core.Resource( 'WebInstance', 'AWS::EC2::Instance', core.Properties({ 'AvailabilityZone': config['AVAILABILITY_ZONE'], 'ImageId': 'ami-d05e75b8', 'InstanceType': 'm3.medium', 'UserData': functions.base64(user_data_script), 'KeyName': config['KEY_NAME'], 'IamInstanceProfile': functions.ref('WebInstanceProfile'), 'Tags': instance_tags, }), core.DependsOn('RDSInstance'), ) cft.resources.instance_profile = core.Resource( 'WebInstanceProfile', 'AWS::IAM::InstanceProfile', core.Properties({ 'Path': '/', 'Roles': [ functions.ref('WebInstanceRole') ] }) ) cft.resources.web_role = core.Resource( 'WebInstanceRole', 'AWS::IAM::Role', core.Properties({ # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-templateexamples "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Service": ["ec2.amazonaws.com"] }, "Action": ["sts:AssumeRole"] }] }, 'ManagedPolicyArns': [ 'arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess', 'arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess', 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' ], 'Path': '/', 'Policies': [ { 'PolicyName': "CreateAccessKey", 'PolicyDocument': { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iam:CreateAccessKey" ], "Resource": [ "*" ] } ] } }, { 'PolicyName': "CreateTags", 'PolicyDocument': { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:CreateTags" ], "Resource": "*" } ] } } ] }) ) cft.resources.smtp_user = core.Resource( 'RefinerySMTPUser', 'AWS::IAM::User', core.Properties({ 'Policies': [{ 'PolicyName': "SESSendingAccess", 'PolicyDocument': { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "ses:SendRawEmail", "Resource": "*" }] } }] }) ) cft.resources.mount = core.Resource( 'RefineryVolume', 'AWS::EC2::VolumeAttachment', core.Properties({ 'Device': '/dev/xvdr', 'InstanceId': functions.ref('WebInstance'), 'VolumeId': functions.ref('RefineryData'), }) ) print(str(cft))
logformat = "%(levelname).4s %(asctime)-15s %(module)10s: %(message)s" if args.log: logging.basicConfig(level=loglevel, format=logformat, filename=args.log) else: logging.basicConfig(level=loglevel, format=logformat) if args.tag: tagged = set(args.tag.split(',')) tagged.add(args.app) else: tagged = None if args.state: progress.init(args.state) tags.load(os.path.join(args.parampath, "tags.txt")) value.init_params(args.parampath, args.app) dev = device.create_device(serial=args.serial) if dev.kind == 'adb': appdb.collect_apps(args.apkspath) elif dev.kind == 'web': appdb.load_urls(os.path.join(args.parampath, "urls.txt")) miner = Miner(dev, args.guispath, args.modelpath, args.tlibpath, args.batch, args.app, args.statpath, args.errpath, args.skippath, args.extrapath, args.mempath, True) if args.explore: miner.explore_mode() else: miner.mine(tagged, args.rounds)
(goodscrcnt, goodwidcnt, badscrcnt, badwidcnt) = get_good_cnt(app, scrhint, widhint, files) seenscrcnt = goodscrcnt + badscrcnt seenwidcnt = goodwidcnt + badwidcnt print("%10s: S%2d(%2d-%2d) W%2d(%2d-%2d) P%2d C%2d" % (app, scrcnt, seenscrcnt, goodscrcnt, widcnt, seenwidcnt, goodwidcnt, paramcnt, confcnt)) totscrcnt += badscrcnt totwidcnt += badwidcnt #totscrcnt += scrcnt - goodscrcnt #totwidcnt += widcnt - goodwidcnt totparamcnt += paramcnt appcnt = len(tags.apps) print("S%d W%d P%d" % (totscrcnt, totwidcnt, totparamcnt)) print("S%.1f W%.1f P%.1f" % (1.0 * totscrcnt / appcnt, 1.0 * totwidcnt / appcnt, 1.0 * totparamcnt / appcnt)) if __name__ == "__main__": parser = argparse.ArgumentParser(description="hintchk") parser.add_argument('--cat', help='category') parser.add_argument('--files', help='files', default='../log/*/') args = parser.parse_args() if args.cat: tags.load("../etc-%s" % args.cat) else: tags.load("../etc") calc_hints(args.cat, args.files)
args.extrapath = "../guis-%s/" % args.extra if args.quiet: print_detail = False if args.reallyquiet: print_detail = False print_per_screen = False print_empty_pt = False print_train = False if args.nopt: nopt = True if args.detail: show_correct_pts = True tags.load(args.tagspath) if args.apps: select_apps = open(args.apps).read().strip().split(',') if args.select: random.seed(args.seed) evalapps = [] for i in range(args.select): evalapp = random.choice(select_apps) evalapps.append(evalapp) print("selected: %s" % evalapps) select_apps = evalapps files = []
def main(): parser = argparse.ArgumentParser(description="Classifier") parser.add_argument('--guispath', help='data path', default="../guis/") parser.add_argument('--tagspath', help='tags path', default="../etc/tags.txt") parser.add_argument('--extrapath', help='extra data path', default="../guis-extra/") parser.add_argument('--app', help='only for app') parser.add_argument('--scrs', help='only for screens') parser.add_argument('--debug', help='debug', default=False, action='store_const', const=True) parser.add_argument('--nopt', help='no pt cache', default=False, action='store_const', const=True) parser.add_argument('--printpt', help='print points', default=False, action='store_const', const=True) parser.add_argument('--onlyfor', help='only print for') parser.add_argument('--analyze', help='show pt for one file', default=None) parser.add_argument('--thres', help='threshold') args = parser.parse_args() if args.debug: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) tags.load(args.tagspath) if args.analyze: (pts, lbls, _, _) = load_points([args.analyze], nopt=True) for i in range(len(pts)): print_point(pts[i], lbls[i]) return if args.thres is not None: config.REGION_SCORE_BOUND = float(args.thres) start_time = time.time() points = {} point_count = 0 files = util.collect_files(args.guispath) # load points first so we have cache later logger.info("Caching points...") (pts, lbls, _, _) = load_points(files, nopt=args.nopt, show_progress=True) if args.printpt: if args.onlyfor: onlyfor = args.onlyfor.split(',') else: onlyfor = None for i in range(len(pts)): if onlyfor is None or lbls[i] in onlyfor: if args.app is None or pts[i]['app'] == args.app: print_point(pts[i], lbls[i]) return logger.info("cached %d points.", len(pts)) logger.info("Load points...") if args.scrs is None: scrs = None else: scrs = args.scrs.split(',') rets = util.parallel_work(functools.partial(load_info, args.app, scrs), files, True, show_progress=True) for ret in rets: if ret is not None: points[ret['app']] = points.get(ret['app'], []) + [ret] point_count += 1 logger.info("Point count: %d, used %.3fs", point_count, time.time() - start_time) apps = list(set(points)) logger.info("Testing, app count %d", len(apps)) if args.app is None and config.parallel: pool = multiprocessing.Pool(processes=config.threads) rets = pool.map( functools.partial(evaluate_app, False, args.guispath, args.extrapath), points.values()) pool.close() else: rets = map( functools.partial(evaluate_app, True, args.guispath, args.extrapath), points.values()) good_cnt = total_cnt = inside_cnt = 0 correct = [] missing = [] extra = [] good_ratio = 0.0 for ret in rets: good_cnt += ret['good'] total_cnt += ret['total'] inside_cnt += ret['inside'] correct += ret['correct'] missing += ret['missing'] extra += ret['extra'] logger.info("app %s: %d/%d avg(scr): %.2f", ret['app'], ret['good'], ret['total'], ret['scr_avg']) good_ratio += ret['scr_avg'] good_ratio /= len(apps) logger.info("Good/Total: %d/%d %.2f", good_cnt, total_cnt, 1.0 * good_cnt / total_cnt) logger.info("Inside/Total: %d/%d %.2f", inside_cnt, total_cnt, 1.0 * inside_cnt / total_cnt) logger.info("Correct: %s", print_hash(count_items(correct))) logger.info("Missing: %s", print_hash(count_items(missing))) logger.info("Extra: %s", print_hash(count_items(extra))) logger.info("Good ratio(app avg): %.3f", good_ratio)
#!/usr/bin/env python3 import appdb import glob import sys import re import tags catname = sys.argv[1] tags.load("../etc-%s" % catname) sys_cfg_lines = 0 cfg_lines = 0 basic_lines = 0 filter_lines = 0 app_match_lines = 0 param_lines = 0 scr_match = 0 wid_match = 0 for app in tags.apps: for line in open("../etc-%s/%s.txt" % (catname, app)): line = line.strip() if not line or line[0] == '#': continue if line[0] == '%' or line[0] == '@': if ('%app_' not in line and '%sys_' not in line and '@app_' not in line and '@sys_' not in line and '@pass_intro' not in line): if line[0] == '%': scr_match += 1 elif line[0] == '@':
wid_re = re.compile('@[^\s]+') for catname in sys.argv[1:]: testcnt = 0 cus_testcnt = 0 totline = 0 cus_totline = 0 app_totline = app_cnt = bridge_totline = bridge_cnt = 0 header = 0 cur_scr = '' used_scr = set() used_wid = set() dirname = "../tlib-%s" % catname tags.load("../etc-%s/tags.txt" % catname) tests = set() found = set() for line in open("%s/tests.txt" % dirname): (scr, name) = line.strip().split('\t') key = scr + ':' + name if not key.startswith('@'): key = '@' + key tests.add(key) for filename in glob.glob("%s/*.feature" % dirname): appname = filename.split('/')[-1].split('.')[0] if appname in appdb.apps: cus_flow = True else:
def print_hint_stat(catname, files): tags.load("../etc-%s" % catname) detail = True latest = {} gstat = {} gestat = {} if files == []: files = glob.glob("../log/*/*.log") for filename in files: appname = os.path.basename(filename).split('.')[0] if appname not in tags.apps: continue t = os.path.basename(os.path.dirname(filename)) if appname not in gstat: gstat[appname] = {} gestat[appname] = {} if appname not in latest or latest[appname] < t: latest[appname] = t get_hint_stat(filename, gstat[appname], gestat[appname]) # for appname in latest: # filename = "../log/%s/%s.log" % (latest[appname], appname) # if appname not in gstat: # gstat[appname] = {} # gestat[appname] = {} # get_hint_stat(filename, gstat[appname], gestat[appname]) # bad_scr = 0 bad_wid = 0 for appname in gstat: stat = gstat[appname] goodcnt = badcnt = 0 for screen in stat: if not tags.validscr(screen): continue #print("%s %s %d+ %d-" % (appname, screen, stat[screen][0], stat[screen][1])) if stat[screen][1] == 0 and stat[screen][0] != 0: if detail: print("%10s SCR %s GOOD (%d)" % (appname, screen, stat[screen][0])) goodcnt += 1 elif stat[screen][1] != 0: if detail: print("%10s SCR %s BAD (%d/%d)" % (appname, screen, stat[screen][0], stat[screen][1])) badcnt += 1 else: if detail: print("%10s SCR %s UNUSED" % (appname, screen)) print("%10s SCR TOTAL %d+ %d-" % (appname, goodcnt, badcnt)) bad_scr += badcnt goodcnt = badcnt = 0 estat = gestat[appname] for element in estat: #print("%s %s %d+ %d-" % (appname, element, estat[element][0], #estat[element][1])) if estat[element][1] == 0 and estat[element][0] != 0: if detail: print("%10s ELEM %s GOOD (%d)" % (appname, element, estat[element][0])) goodcnt += 1 elif estat[element][1] != 0: if detail: print("%10s ELEM %s BAD (%d/%d)" % (appname, element, estat[element][0], estat[element][1])) badcnt += 1 else: if detail: print("%10s ELEM %s UNUSED" % (appname, element)) print("%10s ELEM TOTAL %d+ %d-" % (appname, goodcnt, badcnt)) bad_wid += badcnt print("CAT %s: SCR %d WID %d" % (catname, bad_scr, bad_wid))
elif cmd[0] == ';': util.print_tree(tree, descs) elif cmd[0] == '!': util.printitems(items) elif cmd[0] == 'c': element_clas = elements.getmodel("../model/", "../guis/", None) imgdata = skimage.io.imread(imgfile, as_grey=True) imgdata = skimage.transform.resize(imgdata, (config.height, config.width)) treeinfo = analyze.collect_treeinfo(tree) for itemid in tree: (guess_element, score) = element_clas.classify(scrname, tree, itemid, imgdata, treeinfo) if guess_element != 'NONE': descs[itemid] = guess_element util.print_tree(tree, descs) elif cmd[0] == '?': print(util.describe(items[int(parts[1])])) else: print("Unknown command") viewproc.kill() if __name__ == "__main__": lasttag = None tags.load("../etc-news/tags.txt") for filename in sys.argv[1:]: guess(filename, lasttag)
def main(): parser = argparse.ArgumentParser(description="Miner") parser.add_argument('--tlibpath', help="Test library path", default="../tlib/") parser.add_argument('--apkspath', help="Apps path", default="../apks/") parser.add_argument('--parampath', help="Param file path", default="../etc/") parser.add_argument('app', help="App name") parser.add_argument('--mempath', help="memory path", default="../memory/") parser.add_argument('--erase', help="erase memory of tests") parser.add_argument('--query', help="query screen") parser.add_argument('--reset', help="reset route") args = parser.parse_args() logging.basicConfig(level=logging.INFO) tags.load(os.path.join(args.parampath, "tags.txt")) value.init_params(args.parampath, args.app) appdb.collect_apps(args.apkspath) #appdb.load_urls(os.path.join(args.parampath, "urls.txt")) mine = miner.Miner(None, None, None, args.tlibpath, True, args.app, None, None, None, None, args.mempath, False) if args.erase is not None: if os.path.exists(args.erase): keys = list( filter(lambda x: x, open(args.erase).read().strip().split('\n'))) else: keys = args.erase.split('#') if len(keys) == 0: print("no case to erase!") return print("%d cases to erase" % len(keys)) for key in keys: feature_name, test_name = key.split(':') mine.erase_memory(feature_name, test_name) #mine.print_stat(simple=True) mine.save_memory() print("%d cases erased" % len(keys)) return if args.reset is not None: route_no = int(args.reset) mine.slib.reset_route(route_no) mine.save_memory() return if args.query is not None: attrs = {} if ',' in args.query: screen = args.query.split(',')[0] for entry in args.query.split(',')[1:]: if '=' in entry: key, val = entry.split('=', 1) attrs[key] = val else: if entry[0] == '-': attrs[entry[1:]] = 'false' else: attrs[entry] = 'true' else: screen = args.query if screen != '.': attrs['screen'] = screen mine.slib.query_screen(attrs) return mine.print_stat(simple=True)