Repository: crytic/slither

Issue:
Modify command line arguments
The command line arguments are setup differently than I would expect for Slither. I wonder what people think about changing them? I would have expected some of these changes:

* `--detect` which takes a comma-separated list indicating the checks to run (default: all)
* `--print` which takes a comma-separated list of printers to output (default: ???)
* `--output` which accepts a single parameter of either `stdout` or `json` (default: stdout)
* remove exclusion rules entirely
* calling slither with no arguments should print the help
* reorganize the help screen into groups  of related parameters for easier reading


Discussion:
Personally I'd say this is more consistent with other tools. 
I'm going to take a look at this tomorrow.
In agreement here. I would be in favor of changing following @dguido's suggestion.

It seems like one of the only open items would be around the default `--print` option. I don't think we need to overthink this, just utilizing the contract summary as the default should add utility. If another printer is sought, it's easy to update the parameters. Plus it will support the existing [printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) quite seamlessly. Happy to lend a hand @redshark1802. 

Resolving patch:
diff --git a/slither/__main__.py b/slither/__main__.py
--- a/slither/__main__.py
+++ b/slither/__main__.py
@@ -8,7 +8,7 @@
 import sys
 import traceback
 
-from pkg_resources import iter_entry_points
+from pkg_resources import iter_entry_points, require
 
 from slither.detectors.abstract_detector import (AbstractDetector,
                                                  DetectorClassification,
@@ -19,7 +19,8 @@
 logging.basicConfig()
 logger = logging.getLogger("Slither")
 
-def output_to_markdown(detector_classes):
+
+def output_detectors(detector_classes):
     """
         Pretty print of the detectors to README.md
     """
@@ -27,8 +28,6 @@ def output_to_markdown(detector_classes):
     for detector in detector_classes:
         argument = detector.ARGUMENT
         # dont show the backdoor example
-        if argument == 'backdoor':
-            continue
         help_info = detector.HELP
         impact = detector.IMPACT
         confidence = classification_txt[detector.CONFIDENCE]
@@ -43,7 +42,27 @@ def output_to_markdown(detector_classes):
                                                 help_info,
                                                 classification_txt[impact],
                                                 confidence))
-        idx = idx +1
+        idx = idx + 1
+
+
+def output_printers(printer_classes):
+    """
+        Pretty print of the printers to README.md
+    """
+    printers_list = []
+    for printer in printer_classes:
+        argument = printer.ARGUMENT
+        help_info = printer.HELP
+        printers_list.append((argument, help_info))
+
+    print(printers_list)
+    # Sort by name
+    printers_list = sorted(printers_list, key=lambda element: (element[0]))
+    idx = 1
+    for (argument, help_info) in printers_list:
+        print('{} | `{}` | {} '.format(idx, argument, help_info))
+        idx = idx + 1
+
 
 def process(filename, args, detector_classes, printer_classes):
     """
@@ -64,15 +83,13 @@ def process(filename, args, detector_classes, printer_classes):
 
     results = []
 
-    if printer_classes:
-        slither.run_printers()  # Currently printers does not return results
+    detector_results = slither.run_detectors()
+    detector_results = [x for x in detector_results if x]  # remove empty results
+    detector_results = [item for sublist in detector_results for item in sublist]  # flatten
 
-    elif detector_classes:
-        detector_results = slither.run_detectors()
-        detector_results = [x for x in detector_results if x]  # remove empty results
-        detector_results = [item for sublist in detector_results for item in sublist]  # flatten
+    results.extend(detector_results)
 
-        results.extend(detector_results)
+    slither.run_printers()  # Currently printers does not return results
 
     return results, analyzed_contracts_count
 
@@ -164,8 +181,12 @@ def main_impl(all_detector_classes, all_printer_classes):
     """
     args = parse_args(all_detector_classes, all_printer_classes)
 
-    if args.markdown:
-        output_to_markdown(all_detector_classes)
+    if args.list_detectors:
+        output_detectors(all_detector_classes)
+        return
+
+    if args.list_printers:
+        output_printers(all_printer_classes)
         return
 
     detector_classes = choose_detectors(args, all_detector_classes)
@@ -205,9 +226,6 @@ def main_impl(all_detector_classes, all_printer_classes):
                 (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes)
                 number_contracts += number_contracts_tmp
                 results += results_tmp
-            # if args.json:
-            #    output_json(results, args.json)
-            # exit(results)
 
         else:
             raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename))
@@ -229,17 +247,48 @@ def main_impl(all_detector_classes, all_printer_classes):
 
 def parse_args(detector_classes, printer_classes):
     parser = argparse.ArgumentParser(description='Slither',
-                                     usage="slither.py contract.sol [flag]",
-                                     formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35))
+                                     usage="slither.py contract.sol [flag]")
 
     parser.add_argument('filename',
                         help='contract.sol file')
 
+    parser.add_argument('--version',
+                        help='displays the current version',
+                        version=require('slither-analyzer')[0].version,
+                        action='version')
+
     parser.add_argument('--solc',
                         help='solc path',
                         action='store',
                         default='solc')
 
+    parser.add_argument('--detectors',
+                        help='Comma-separated list of detectors, defaults to all, '
+                             'available detectors: {}'.format(', '.join(d.ARGUMENT for d in detector_classes)),
+                        action='store',
+                        dest='detectors_to_run',
+                        default='all')
+
+    parser.add_argument('--printers',
+                        help='Comma-separated list fo contract information printers, '
+                             'defaults to contract-summary and can be disabled by using \'none\', '
+                             'available printers: {}'.format(', '.join(d.ARGUMENT for d in printer_classes)),
+                        action='store',
+                        dest='printers_to_run',
+                        default='contract-summary')
+
+    parser.add_argument('--output',
+                        help='Define output encoding',
+                        action='store',
+                        choices=['stdout', 'json'],
+                        default='stdout')
+
+    parser.add_argument('--exclude-detectors',
+                        help='Comma-separated list of detectors that should be excluded',
+                        action='store',
+                        dest='detectors_to_exclude',
+                        default='')
+
     parser.add_argument('--solc-args',
                         help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".',
                         action='store',
@@ -280,34 +329,6 @@ def parse_args(detector_classes, printer_classes):
                         action='store_true',
                         default=False)
 
-    for detector_cls in detector_classes:
-        detector_arg = '--detect-{}'.format(detector_cls.ARGUMENT)
-        detector_help = '{}'.format(detector_cls.HELP)
-        parser.add_argument(detector_arg,
-                            help=detector_help,
-                            action="append_const",
-                            dest="detectors_to_run",
-                            const=detector_cls.ARGUMENT)
-
-    # Second loop so that the --exclude are shown after all the detectors
-    for detector_cls in detector_classes:
-        exclude_detector_arg = '--exclude-{}'.format(detector_cls.ARGUMENT)
-        exclude_detector_help = 'Exclude {} detector'.format(detector_cls.ARGUMENT)
-        parser.add_argument(exclude_detector_arg,
-                            help=exclude_detector_help,
-                            action="append_const",
-                            dest="detectors_to_exclude",
-                            const=detector_cls.ARGUMENT)
-
-    for printer_cls in printer_classes:
-        printer_arg = '--printer-{}'.format(printer_cls.ARGUMENT)
-        printer_help = 'Print {}'.format(printer_cls.HELP)
-        parser.add_argument(printer_arg,
-                            help=printer_help,
-                            action="append_const",
-                            dest="printers_to_run",
-                            const=printer_cls.ARGUMENT)
-
     # debugger command
     parser.add_argument('--debug',
                         help=argparse.SUPPRESS,
@@ -319,15 +340,44 @@ def parse_args(detector_classes, printer_classes):
                         action="store_true",
                         default=False)
 
-    return parser.parse_args()
+    parser.add_argument('--list-detectors',
+                        help='List available detectors',
+                        action='store_true',
+                        default=False)
+
+    parser.add_argument('--list-printers',
+                        help='List available printers',
+                        action='store_true',
+                        default=False)
+
+    if len(sys.argv) == 1:
+        parser.print_help(sys.stderr)
+        sys.exit(1)
+
+    args = parser.parse_args()
+
+    return args
 
 
 def choose_detectors(args, all_detector_classes):
     # If detectors are specified, run only these ones
-    if args.detectors_to_run:
-        return [d for d in all_detector_classes if d.ARGUMENT in args.detectors_to_run]
 
-    detectors_to_run = all_detector_classes
+    detectors_to_run = []
+    detectors = {d.ARGUMENT: d for d in all_detector_classes}
+
+    if args.detectors_to_run == 'all':
+        detectors_to_run = all_detector_classes
+        detectors_excluded = args.detectors_to_exclude.split(',')
+        for d in detectors:
+            if d in detectors_excluded:
+                detectors_to_run.remove(detectors[d])
+    else:
+        for d in args.detectors_to_run.split(','):
+            if d in detectors:
+                detectors_to_run.append(detectors[d])
+            else:
+                raise Exception('Error: {} is not a detector'.format(d))
+        return detectors_to_run
 
     if args.exclude_informational:
         detectors_to_run = [d for d in detectors_to_run if
@@ -348,11 +398,18 @@ def choose_detectors(args, all_detector_classes):
 
 
 def choose_printers(args, all_printer_classes):
-    # by default, dont run any printer
     printers_to_run = []
-    if args.printers_to_run:
-        printers_to_run = [p for p in all_printer_classes if
-                           p.ARGUMENT in args.printers_to_run]
+
+    # disable default printer
+    if args.printers_to_run == 'none':
+        return printers_to_run
+
+    printers = {p.ARGUMENT: p for p in all_printer_classes}
+    for p in args.printers_to_run.split(','):
+        if p in printers:
+            printers_to_run.append(printers[p])
+        else:
+            raise Exception('Error: {} is not a printer'.format(p))
     return printers_to_run
 
 


Test patch:
diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh
--- a/scripts/travis_test.sh
+++ b/scripts/travis_test.sh
@@ -3,79 +3,79 @@
 ### Test Detectors
 
 
-slither tests/uninitialized.sol --disable-solc-warnings --detect-uninitialized-state
+slither tests/uninitialized.sol --disable-solc-warnings --detectors uninitialized-state
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
 # contains also the test for the suicidal detector
-slither tests/backdoor.sol --disable-solc-warnings --detect-backdoor
+slither tests/backdoor.sol --disable-solc-warnings --detectors backdoor
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/pragma.0.4.24.sol --disable-solc-warnings --detect-pragma
+slither tests/pragma.0.4.24.sol --disable-solc-warnings --detectors pragma
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/old_solc.sol.json --solc-ast --detect-solc-version
+slither tests/old_solc.sol.json --solc-ast --detectors solc-version
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/reentrancy.sol --disable-solc-warnings --detect-reentrancy
+slither tests/reentrancy.sol --disable-solc-warnings --detectors reentrancy
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detect-uninitialized-storage
+slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detectors uninitialized-storage
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/tx_origin.sol --disable-solc-warnings --detect-tx-origin
+slither tests/tx_origin.sol --disable-solc-warnings --detectors tx-origin
 if [ $? -ne 2 ]; then
     exit 1
 fi
 
-slither tests/unused_state.sol --detect-unused-state
+slither tests/unused_state.sol --detectors unused-state
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/locked_ether.sol --detect-locked-ether
+slither tests/locked_ether.sol --detectors locked-ether
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/arbitrary_send.sol --disable-solc-warnings --detect-arbitrary-send
+slither tests/arbitrary_send.sol --disable-solc-warnings --detectors arbitrary-send
 if [ $? -ne 2 ]; then
     exit 1
 fi
 
 
-slither tests/inline_assembly_contract.sol --disable-solc-warnings --detect-assembly
+slither tests/inline_assembly_contract.sol --disable-solc-warnings --detectors assembly
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/inline_assembly_library.sol --disable-solc-warnings --detect-assembly
+slither tests/inline_assembly_library.sol --disable-solc-warnings --detectors assembly
 if [ $? -ne 2 ]; then
     exit 1
 fi
 
-slither tests/naming_convention.sol --disable-solc-warnings --detect-naming-convention
+slither tests/naming_convention.sol --disable-solc-warnings --detectors naming-convention
 if [ $? -ne 10 ]; then
     exit 1
 fi
 
-slither tests/low_level_calls.sol --disable-solc-warnings --detect-low-level-calls
+slither tests/low_level_calls.sol --disable-solc-warnings --detectors low-level-calls
 if [ $? -ne 1 ]; then
     exit 1
 fi
 
-slither tests/const_state_variables.sol --detect-const-candidates-state
+slither tests/const_state_variables.sol --detectors const-candidates-state
 if [ $? -ne 2 ]; then
     exit 1
 fi