def __set(args): env_var = create_environment_variable(args.VARIABLE) if env_var is None: raise ValueError("This environment variable cannot or should not " "be managed.") env_var.value = args.VALUE get_current_shell().set_env_var(env_var)
def __remove(args): env_var = create_environment_variable(args.VARIABLE) if env_var is None: raise ValueError("This environment variable cannot or should not " "be managed.") if not isinstance(env_var, ArrayEnvVar): raise NotImplementedError("'add' and 'remove' operations are only " "applicable to array-like environmental " "variables.") for val in args.VALUE: env_var.remove_value(val) get_current_shell().set_env_var(env_var)
def __create_track_subcommand(main_parser): parser = main_parser.add_parser( name='track', description="Change a variable's tracking status. A tracked " "variable's value changes is reflected in Envprobe " "saves.", help="Change a variable's tracking status." ) parser.add_argument( 'VARIABLE', type=str, help="The variable which is to be tracked, e.g. PATH." ) if get_current_shell(): # Only allow the changing the non-global configuration if there # is a shell that is loaded. parser.add_argument('-g', '--global', dest='global_scope', action='store_true', help="Save the tracking status into your user " "configuration, not to the settings of the " "current Shell.") mgroup = parser.add_argument_group('additional tracking settings') mgroup = mgroup.add_mutually_exclusive_group() mgroup.add_argument('-i', '--ignore', action='store_true', help="Set the variable to be ignored instead of " "tracked. An ignored variable's value changes " "are not reflected in Envprove saves.") mgroup.add_argument('-d', '--default', action='store_true', help="Remove both the track and ignore status of " "VARIABLE. After this, the user's or shell's " "(depending on whether '-g' was specified) " "default tracking behaviour will be " "applicable.") parser.set_defaults(func=__track) if not get_current_shell(): # If a shell could not be loaded, default to changing the global # state. parser.set_defaults(global_scope=True) global_config.REGISTERED_COMMANDS.append('track')
def get_common_epilogue_or_die(): epilogue = None shell = get_current_shell() if len(sys.argv) == 1 or \ (len(sys.argv) == 2 and sys.argv[1] in ['-h', '--help']): if shell is None: epilogue = "You are currently using `envprobe` in a shell that " \ "does not have it enabled. Please refer to the " \ "README on how to enable Envprobe." if len(sys.argv) == 1: print( "To see what commands `envprobe` can do, specify " "'--help'.", file=sys.stderr) elif shell is False: epilogue = "You are currently using an unknown shell, but " \ "your environment claims Envprobe is enabled. " \ "Stop hacking your variables! :)" else: epilogue = "You are currently using a '{0}' shell, and Envprobe " \ "is enabled!".format(shell.shell_type) if int(os.environ.get('_ENVPROBE', 0)) != 1: # If the user is not running the command through an alias, # present an error. We don't want users to randomly run # envprobe if it is enabled and set up. print("You are in an environment where `envprobe` is " "enabled, but you used the command '{0}' to run " "Envprobe, instead of `envprobe`.".format(sys.argv[0]), file=sys.stderr) sys.exit(2) return epilogue
def __track(args): tracking = TrackingOverlay(get_current_shell()) if args.ignore: tracking.ignore(args.VARIABLE, args.global_scope) elif args.default: tracking.make_default(args.VARIABLE, args.global_scope) else: tracking.track(args.VARIABLE, args.global_scope) tracking.flush(args.global_scope)
def __add(args): env_var = create_environment_variable(args.VARIABLE) if env_var is None: raise ValueError("This environment variable cannot or should not " "be managed.") if not isinstance(env_var, ArrayEnvVar): raise NotImplementedError("'add' and 'remove' operations are only " "applicable to array-like environmental " "variables.") for val in args.VALUE: env_var.insert_at(args.position, val) if args.position >= 0: # If the arguments are appended with a positive index, we insert # the values in order. If the arguments are inserted at a negative # index, the position must not be modified, because the arguments # get shifted with the insert. args.position += 1 get_current_shell().set_env_var(env_var)
def create_subcommand_parser(main_parser): if not get_current_shell(): return # Only expose these commands of this module if the user is running # envprobe in a known valid shell. __create_get_subcommand(main_parser) __create_set_subcommand(main_parser) __create_add_subcommand(main_parser) __create_remove_subcommand(main_parser) __create_undefine_subcommand(main_parser)
def create_subcommand_parser(main_parser): __create_list_subcommand(main_parser) if get_current_shell(): # Only expose these commands of this module if the user is running # envprobe in a known valid shell. __create_load_subcommand(main_parser) __create_diff_subcommand(main_parser) __create_save_subcommand(main_parser) __create_delete_subcommand(main_parser)
def __create_default_tracking_subcommand(main_parser): parser = main_parser.add_parser( name='default-tracking', description="Change the default tracking behaviour of variables.", help="Change the default tracking behaviour of variables." ) ngroup = parser.add_mutually_exclusive_group(required=True) ngroup.add_argument('-t', '--track', '-e', '--enable', dest='track', action='store_true', help="Set the default behaviour to track all " "(not explicitly ignored) variables.") ngroup.add_argument('-i', '--ignore', '-d', '--disable', dest='ignore', action='store_true', help="Set the default behaviour to ignore all " "(not explicitly tracked) variables.") if get_current_shell(): # Only allow the changing the non-global configuration if there # is a shell that is loaded. parser.add_argument('-g', '--global', dest='global_scope', action='store_true', help="Save the tracking status into your user " "configuration, not to the settings of the " "current Shell.") parser.set_defaults(func=__change_default) if not get_current_shell(): # If a shell could not be loaded, default to changing the global # state. parser.set_defaults(global_scope=True) global_config.REGISTERED_COMMANDS.append('default-tracking')
def __undefine(args): env_var = create_environment_variable(args.VARIABLE) if env_var is None: raise ValueError("This environment variable cannot or should not " "be managed.") get_current_shell().undefine_env_var(env_var)
def __load(args): def bool_prompt(): response = False user_input = input("Apply this change? (y/N) ").lower() if user_input == 'y' or user_input == 'yes': response = True return response shell = get_current_shell() tracking = TrackingOverlay(shell) env = environment.Environment(shell) var_names = __clean_variable_list(args.variable) with Save(args.name, read_only=True) as save: if save is None: print("Error! The save '%s' cannot be opened, perhaps it is " "being modified by another process!" % args.name, file=sys.stderr) return if len(save) == 0: print("The save '%s' does not exist!" % args.name) for variable_name in save: if var_names and variable_name not in var_names: continue if not var_names and not tracking.is_tracked(variable_name): continue # The 'saved' variable is used to update the environment's # "saved" state so the loaded change won't be a difference. variable_saved = create_environment_variable(variable_name, env.saved_env) # The 'shell' variable is used to update the actual value the # user experiences in the shell. (This distinction is used # because there might be changes in the current shell which were # never "saved" into the state file or a save.) variable_shell = create_environment_variable(variable_name, env.current_env) if save[variable_name] == Save.UNSET: if args.patch or args.dry_run: print("Variable \"%s\" will be unset (from value: '%s')" % (variable_name, variable_shell.value)) if not args.dry_run and (not args.patch or bool_prompt()): env.apply_change(variable_saved, remove=True) shell.undefine_env_var(variable_shell) continue # Read the change from the save and apply it to the variable. value = save[variable_name] current_value = variable_shell.value if not isinstance(value, dict): # Single variable changes contain the NEW value in the save. # For the sake of user communication here, regard a list # containing a single string as a single string. if isinstance(value, list) and len(value) == 1: value = value[0] if isinstance(current_value, list) and \ len(current_value) == 1: current_value = current_value[0] if args.patch or args.dry_run: if variable_name not in env.current_env: print("New variable \"%s\" will be set to value: " "'%s'." % (variable_name, value)) elif value == current_value: # Don't change something that already has the new # value. continue else: print("Variable \"%s\" will be changed to value: " "'%s' (previous value was: '%s')" % (variable_name, value, current_value)) if not args.dry_run and (not args.patch or bool_prompt()): variable_saved.value = value variable_shell.value = value env.apply_change(variable_saved) shell.set_env_var(variable_shell) else: # Complex changes are represented in a dict. if not isinstance(variable_saved, ArrayEnvVar) or \ not isinstance(variable_saved, ArrayEnvVar): raise TypeError("Cannot apply a complex add/remove " "change to a variable which is not an " "array!") insert_idx = 0 for add in value['add']: if add in current_value: # Ignore adding something that is already there. continue if args.patch or args.dry_run: print("In variable \"%s\", the value (component) " "'%s' will be added." % (variable_name, add)) if not args.dry_run and (not args.patch or bool_prompt()): variable_saved.insert_at(insert_idx, add) variable_shell.insert_at(insert_idx, add) insert_idx += 1 env.apply_change(variable_saved) shell.set_env_var(variable_shell) for remove in value['remove']: if remove not in current_value: # Ignore removing something that is not there. continue if args.patch or args.dry_run: print("In variable \"%s\", the value (component) " "'%s' will be removed." % (variable_name, remove)) if not args.dry_run and (not args.patch or bool_prompt()): variable_saved.remove_value(remove) variable_shell.remove_value(remove) env.apply_change(variable_saved) shell.set_env_var(variable_shell) # After loading, the user's "known" environment has changed, so it has # to be flushed. env.flush()
def __diff(args): TYPES = environment.VariableDifferenceType shell = get_current_shell() tracking = TrackingOverlay(shell) env = environment.Environment(shell) diffs = env.diff() var_names = __clean_variable_list(args.variable) for variable_name in sorted(list(diffs.keys())): if var_names and variable_name not in var_names: continue if not var_names and not tracking.is_tracked(variable_name): continue change = diffs[variable_name] diff = change.differences if args.type == 'normal': kind = diffs[variable_name].type if kind == TYPES.ADDED: kind = '+ Added:' elif kind == TYPES.REMOVED: kind = '- Removed:' elif kind == TYPES.CHANGED: kind = '! Modified:' print("%s %s" % (kind.ljust(11), variable_name)) if change.is_new() or change.is_unset(): # If only a remove or an addition took place, show the # new value. print(" value: %s" % diff[0][1]) elif change.is_simple_change(): # If the difference is exactly a single change from a # value to another, just show the change. print(" from: %s\n to: %s" % (diff[1][1], diff[0][1])) else: for action, value in diff: if action == ' ': # Do not show "keep" or "unchanged" lines. continue elif action == '+': print(" added %s" % value) elif action == '-': print(" removed %s" % value) print() elif args.type == 'unified': old_name, new_name = variable_name, variable_name old_start, old_count, new_start, new_count = \ 1, len(diff), 1, len(diff) if diffs[variable_name].type == TYPES.ADDED: old_name = '(new variable)' old_start, old_count = 0, 0 elif diffs[variable_name].type == TYPES.REMOVED: new_name = '(variable unset)' new_start, new_count = 0, 0 print("--- %s" % old_name) print("+++ %s" % new_name) print("@@ -%d,%d +%d,%d @@" % (old_start, old_count, new_start, new_count)) for difference in diff: print("%s %s" % difference) print()
def __save(args): def bool_prompt(): response = False user_input = input("Save this change? (y/N) ").lower() if user_input == 'y' or user_input == 'yes': response = True return response shell = get_current_shell() tracking = TrackingOverlay(shell) env = environment.Environment(shell) diffs = env.diff() var_names = __clean_variable_list(args.variable) with Save(args.name, read_only=False) as save: if save is None: print("Error! The save '%s' cannot be opened, perhaps it is " "being modified by another process!" % args.name, file=sys.stderr) return for variable_name in sorted(list(diffs.keys())): if var_names and variable_name not in var_names: continue if not var_names and not tracking.is_tracked(variable_name): continue change = diffs[variable_name] diff = change.differences variable_saved = create_environment_variable(variable_name, env.saved_env) # First, transform the change to something that can later be # applied. if change.is_new(): # In case a new variable was introduced, we only care # about the new, set value. if args.patch: print("Variable \"%s\" set to value: '%s'." % (variable_name, change.new_value)) if not args.patch or bool_prompt(): save[variable_name] = change.new_value variable_saved.value = change.new_value env.apply_change(variable_saved) elif change.is_unset(): # If a variable was removed from the environment, it # usually doesn't matter what its value was, only the fact # that it was removed. if args.patch: print("Variable \"%s\" unset (from value: '%s')" % (variable_name, change.old_value)) if not args.patch or bool_prompt(): del save[variable_name] env.apply_change(variable_saved, remove=True) elif change.is_simple_change(): # If the change was a simple change that changed a # variable from something to something we are still only # interested in the new value. (This is environment # variables, not Git!) if args.patch: print("Variable \"%s\" changed to value: '%s' " "(previous value was: '%s')" % (variable_name, change.new_value, change.old_value)) if not args.patch or bool_prompt(): save[variable_name] = change.new_value variable_saved.value = change.new_value env.apply_change(variable_saved) else: if not isinstance(variable_saved, ArrayEnvVar) or \ not isinstance(variable_saved, ArrayEnvVar): raise TypeError("Cannot apply a complex add/remove " "change to a variable which is not an " "array!") # For complex changes, such as removal and addition of # multiple PATHs, both differences must be saved. This # ensures that if a user's particular save depends on # something that's usually seen is to not be seen, we can # handle it. # (In most cases, people only append new values to their # various PATHs...) save[variable_name] = {'add': [], 'remove': []} for mode, value in diff: key = None passive = None if mode == ' ': # Ignore unchanged values. continue elif mode == '+': key = 'add' passive = "added" elif mode == '-': key = 'remove' passive = "removed" if args.patch: print("In variable \"%s\", the value (component) " "'%s' was %s." % (variable_name, value, passive)) if not args.patch or bool_prompt(): save[variable_name][key].append(value) if key == 'add': variable_saved.insert_at(0, value) elif key == 'remove': variable_saved.remove_value(value) env.apply_change(variable_saved) # Write the named state save file to the disk. save.flush() # Write the changed current environment's now saved changes to the # shell's known state, so changes saved here are no longer shown as # diffs. env.flush()
def __change_default(args): tracking = TrackingOverlay(get_current_shell()) tracking.set_default(args.track, args.global_scope) tracking.flush(args.global_scope)