@@ -689,7 +689,10 @@ def should_ignore_for_mutation(self, path):
689689 return False
690690
691691
692- def config_reader ():
692+ def config_reader (cli_overrides = None ):
693+ if cli_overrides is None :
694+ cli_overrides = {}
695+
693696 path = Path ('pyproject.toml' )
694697 if path .exists ():
695698 if sys .version_info >= (3 , 11 ):
@@ -705,6 +708,9 @@ def config_reader():
705708 pass
706709 else :
707710 def s (key , default ):
711+ # try CLI overrides first
712+ if key in cli_overrides and cli_overrides [key ] is not None :
713+ return cli_overrides [key ]
708714 try :
709715 result = config [key ]
710716 except KeyError :
@@ -716,6 +722,9 @@ def s(key, default):
716722 config_parser .read ('setup.cfg' )
717723
718724 def s (key , default ):
725+ # try CLI overrides first
726+ if key in cli_overrides and cli_overrides [key ] is not None :
727+ return cli_overrides [key ]
719728 try :
720729 result = config_parser .get ('mutmut' , key )
721730 except (NoOptionError , NoSectionError ):
@@ -733,13 +742,13 @@ def s(key, default):
733742 return s
734743
735744
736- def ensure_config_loaded ():
745+ def ensure_config_loaded (cli_overrides = None ):
737746 if mutmut .config is None :
738- mutmut .config = load_config ()
747+ mutmut .config = load_config (cli_overrides )
739748
740749
741- def load_config ():
742- s = config_reader ()
750+ def load_config (cli_overrides = None ):
751+ s = config_reader (cli_overrides )
743752
744753 return Config (
745754 do_not_mutate = s ('do_not_mutate' , []),
@@ -765,8 +774,36 @@ def load_config():
765774
766775@click .group ()
767776@click .version_option (mutmut .__version__ )
768- def cli ():
769- pass
777+ @click .option ('--debug' , is_flag = True , help = 'Enable debug mode' )
778+ @click .option ('--paths-to-mutate' , multiple = True , help = 'Paths to mutate' )
779+ @click .option ('--tests-dir' , multiple = True , help = 'Directory with tests' )
780+ @click .pass_context
781+ def cli (ctx , debug , paths_to_mutate , tests_dir ):
782+ ctx .ensure_object (dict )
783+
784+ cli_overrides = {}
785+ if debug :
786+ cli_overrides ['debug' ] = True
787+ if paths_to_mutate :
788+ split_paths = []
789+ for path in paths_to_mutate :
790+ if ',' in path :
791+ split_paths .extend (p .strip () for p in path .split (',' ))
792+ else :
793+ split_paths .append (path )
794+ cli_overrides ['paths_to_mutate' ] = split_paths
795+ if tests_dir :
796+ # Support comma-separated values
797+ split_tests = []
798+ for test_dir in tests_dir :
799+ if ',' in test_dir :
800+ split_tests .extend (t .strip () for t in test_dir .split (',' ))
801+ else :
802+ split_tests .append (test_dir )
803+ cli_overrides ['tests_dir' ] = split_tests
804+
805+ ctx .obj ['cli_overrides' ] = cli_overrides
806+
770807
771808
772809def run_stats_collection (runner , tests = None ):
@@ -881,9 +918,11 @@ def estimated_worst_case_time(mutant_name):
881918
882919@cli .command ()
883920@click .argument ('mutant_names' , required = False , nargs = - 1 )
884- def print_time_estimates (mutant_names ):
921+ @click .pass_context
922+ def print_time_estimates (ctx , mutant_names ):
885923 assert isinstance (mutant_names , (tuple , list )), mutant_names
886- ensure_config_loaded ()
924+ cli_overrides = ctx .obj .get ('cli_overrides' , {})
925+ ensure_config_loaded (cli_overrides )
887926
888927 runner = PytestRunner ()
889928 runner .prepare_main_test_run ()
@@ -947,16 +986,18 @@ def inner_timout_checker():
947986@cli .command ()
948987@click .option ('--max-children' , type = int )
949988@click .argument ('mutant_names' , required = False , nargs = - 1 )
950- def run (mutant_names , * , max_children ):
989+ @click .pass_context
990+ def run (ctx , mutant_names , * , max_children ):
951991 assert isinstance (mutant_names , (tuple , list )), mutant_names
952- _run (mutant_names , max_children )
992+ cli_overrides = ctx .obj .get ('cli_overrides' , {})
993+ _run (mutant_names , max_children , cli_overrides )
953994
954995# separate function, so we can call it directly from the tests
955- def _run (mutant_names : Union [tuple , list ], max_children : Union [None , int ]):
996+ def _run (mutant_names : Union [tuple , list ], max_children : Union [None , int ], cli_overrides = None ):
956997 # TODO: run no-ops once in a while to detect if we get false negatives
957998 # TODO: we should be able to get information on which tests killed mutants, which means we can get a list of tests and how many mutants each test kills. Those that kill zero mutants are redundant!
958999 os .environ ['MUTANT_UNDER_TEST' ] = 'mutant_generation'
959- ensure_config_loaded ()
1000+ ensure_config_loaded (cli_overrides )
9601001
9611002 if max_children is None :
9621003 max_children = os .cpu_count () or 4
@@ -1138,8 +1179,10 @@ def tests_for_mutant_names(mutant_names):
11381179
11391180@cli .command ()
11401181@click .option ('--all' , default = False )
1141- def results (all ):
1142- ensure_config_loaded ()
1182+ @click .pass_context
1183+ def results (ctx , all ):
1184+ cli_overrides = ctx .obj .get ('cli_overrides' , {})
1185+ ensure_config_loaded (cli_overrides )
11431186 for path in walk_source_files ():
11441187 if not str (path ).endswith ('.py' ):
11451188 continue
@@ -1233,17 +1276,20 @@ def get_diff_for_mutant(mutant_name, source=None, path=None):
12331276
12341277@cli .command ()
12351278@click .argument ('mutant_name' )
1236- def show (mutant_name ):
1237- ensure_config_loaded ()
1279+ @click .pass_context
1280+ def show (ctx , mutant_name ):
1281+ cli_overrides = ctx .obj .get ('cli_overrides' , {})
1282+ ensure_config_loaded (cli_overrides )
12381283 print (get_diff_for_mutant (mutant_name ))
12391284 return
12401285
12411286
12421287@cli .command ()
12431288@click .argument ('mutant_name' )
1244- def apply (mutant_name ):
1245- # try:
1246- ensure_config_loaded ()
1289+ @click .pass_context
1290+ def apply (ctx , mutant_name ):
1291+ cli_overrides = ctx .obj .get ('cli_overrides' , {})
1292+ ensure_config_loaded (cli_overrides )
12471293 apply_mutant (mutant_name )
12481294 # except FileNotFoundError as e:
12491295 # print(e)
@@ -1275,8 +1321,10 @@ def apply_mutant(mutant_name):
12751321
12761322@cli .command ()
12771323@click .option ("--show-killed" , is_flag = True , default = False , help = "Display killed mutants." )
1278- def browse (show_killed ):
1279- ensure_config_loaded ()
1324+ @click .pass_context
1325+ def browse (ctx , show_killed ):
1326+ cli_overrides = ctx .obj .get ('cli_overrides' , {})
1327+ ensure_config_loaded (cli_overrides )
12801328
12811329 from textual .app import App
12821330 from textual .containers import Container
@@ -1333,7 +1381,7 @@ def on_mount(self):
13331381 self .populate_files_table ()
13341382
13351383 def read_data (self ):
1336- ensure_config_loaded ()
1384+ ensure_config_loaded (cli_overrides )
13371385 self .source_file_mutation_data_and_stat_by_path = {}
13381386 self .path_by_name = {}
13391387
@@ -1388,7 +1436,7 @@ def on_data_table_row_highlighted(self, event):
13881436 path = self .path_by_name .get (event .row_key .value )
13891437
13901438 def load_thread ():
1391- ensure_config_loaded ()
1439+ ensure_config_loaded (cli_overrides )
13921440 try :
13931441 d = get_diff_for_mutant (event .row_key .value , path = path )
13941442 if event .row_key .value == self .loading_id :
@@ -1427,7 +1475,7 @@ def action_retest_module(self):
14271475 self .retest (self .get_mutant_name_from_selection ().rpartition ('.' )[0 ] + '.*' )
14281476
14291477 def action_apply_mutant (self ):
1430- ensure_config_loaded ()
1478+ ensure_config_loaded (cli_overrides )
14311479 # noinspection PyTypeChecker
14321480 mutants_table : DataTable = self .query_one ('#mutants' )
14331481 if mutants_table .cursor_row is None :
0 commit comments