class GitGud: def __init__(self): self.file_operator = get_operator( ) # Only gets operator if in a valid gitgud repo self.parser = argparse.ArgumentParser(prog='git gud') subparsers = self.parser.add_subparsers(title='Subcommands', metavar='<command>', dest='command') # TODO Add git gud help <command>, which would return the same output as git gud <command> -- help # TODO Display help message for subcommand when it fails. # ie `git gud load level1 challenge1 random-input` should have output similar to `git gud load --help` start_parser = subparsers.add_parser('start', help='Git started!') status_parser = subparsers.add_parser( 'status', help='Print out the current level') instructions_parser = subparsers.add_parser( 'instructions', help='Show the instructions for the current level') reset_parser = subparsers.add_parser('reset', help='Reset the current level') test_parser = subparsers.add_parser( 'test', help= 'Test to see if you\'ve successfully completed the current level') progress_parser = subparsers.add_parser( 'progress', help='Continue to the next level') levels_parser = subparsers.add_parser('levels', help='List levels') challenges_parser = subparsers.add_parser( 'challenges', help= 'List challenges in current level or in other level if specified') load_parser = subparsers.add_parser( 'load', help='Load a specific level or challenge') commit_parser = subparsers.add_parser( 'commit', help='Quickly create and commit a file') goal_parser = subparsers.add_parser( 'goal', help='Show a description of the current goal') show_tree_parser = subparsers.add_parser( 'show-tree', help='Show the current state of the branching tree') start_parser.add_argument('--force', action='store_true') challenges_parser.add_argument('level_name', metavar='level', nargs='?') load_parser.add_argument('level_name', metavar='level', help='Level to load') load_parser.add_argument('challenge_name', metavar='challenge', nargs='?', help='Challenge to load') commit_parser.add_argument('file', nargs='?') self.command_dict = { 'start': self.handle_start, 'status': self.handle_status, 'instructions': self.handle_instructions, 'reset': self.handle_reset, 'test': self.handle_test, 'progress': self.handle_progress, 'levels': self.handle_levels, 'challenges': self.handle_challenges, 'load': self.handle_load, 'commit': self.handle_commit, 'goal': self.handle_goal, 'show_tree': self.handle_show_tree, } def is_initialized(self): return self.file_operator is not None def assert_initialized(self): if not self.is_initialized(): raise InitializationError( "Git gud not initialized. Use \"git gud start\" to initialize") def handle_start(self, args): if not args.force: # We aren't forcing if self.file_operator: print('Repo {} already initialized for git gud.'.format( self.file_operator.path)) print('Use --force to initialize {}.'.format(os.getcwd())) return self.file_operator = Operator(os.getcwd()) if os.path.exists(self.file_operator.git_path): # Current directory is a git repo print( 'Currently in a git repo. Use --force to force initialize here.' ) return if os.path.exists(self.file_operator.gg_path): # Current directory is a git repo print( 'Git gud has already initialized. Use --force to force initialize again.' ) return if len(os.listdir(self.file_operator.path)) != 0: print( 'Current directory is nonempty. Use --force to force initialize here.' ) return else: print('Force initializing git gud.') # After here, we initialize everything self.file_operator.initialize() if not os.path.exists(self.file_operator.gg_path): os.mkdir(self.file_operator.gg_path) with open(self.file_operator.last_commit_path, 'w+') as commit_file: commit_file.write('0') # First commit will be 1 with open(self.file_operator.level_path, 'w+') as level_file: level_file.write('intro commits') print('Git Gud successfully setup in {}'.format(os.getcwd())) self.file_operator.get_challenge().setup(self.file_operator) def handle_status(self, args): if self.is_initialized(): challenge_name = self.file_operator.get_challenge().full_name() print("Currently on challenge: \"{}\"".format(challenge_name)) else: print("Git gud not initialized.") print("Initialize using \"git gud start\"") def handle_instructions(self, args): self.assert_initialized() self.file_operator.get_challenge().instructions() def handle_reset(self, args): self.assert_initialized() challenge = self.file_operator.get_challenge() print("Resetting...") challenge.setup(self.file_operator) def handle_test(self, args): self.assert_initialized() challenge = self.file_operator.get_challenge() if challenge.test(self.file_operator): print( "Level complete! `git gud progress` to advance to the next level" ) else: print( "Level not complete, keep trying. `git gud reset` to start from scratch." ) def handle_progress(self, args): self.assert_initialized() print("Progressing to next level...") challenge = self.file_operator.get_challenge() next_challenge = challenge.next_challenge if next_challenge is not None: next_challenge.setup(self.file_operator) self.file_operator.write_challenge(next_challenge) else: print("Wow! You've complete every challenge, congratulations!") print( "If you want to keep learning git, why not try contributing to git-gud by forking us at https://github.com/bthayer2365/git-gud/" ) print( "We're always looking for contributions and are more than happy to accept both pull requests and suggestions!" ) def handle_levels(self, args): cur_level = self.file_operator.get_challenge().level print("Currently on level: \"{}\"\n".format(cur_level.name)) for level in all_levels.values(): # TODO Make pretty # TODO Add description print(level.name, ": {} challenges".format(len(level.challenges))) def handle_challenges(self, args): if args.level_name is None: level = self.file_operator.get_challenge().level else: level = all_levels[args.level_name] print("Printing challenges for level: \"{level.name}\"\n".format( level.name)) for challenge in level.challenges.values(): print(challenge.name) def handle_load(self, args): self.assert_initialized() level = all_levels[args.level_name] if args.challenge_name is not None: level.challenges[args.challenge_name].setup(self.file_operator) else: first_level = next(iter(level.challenges.values())) first_level.setup(self.file_operator) def handle_commit(self, args): self.assert_initialized() last_commit = self.file_operator.get_last_commit() commit_name = str(int(last_commit) + 1) if args.file is not None: try: int(args.file) commit_name = args.file except ValueError: pass print("Simulating: Create file \"{}\"".format(commit_name)) print("Simulating: git add {}".format(commit_name)) print("Simulating: git commit -m \"{}\"".format(commit_name)) self.file_operator.add_and_commit(commit_name) # Check if the newest commit is greater than the last_commit, if yes, then write if int(commit_name) > int(last_commit): self.file_operator.write_last_commit(commit_name) def handle_goal(self, args): self.assert_initialized() raise NotImplementedError def handle_show_tree(self, args): raise NotImplementedError def parse(self): args = self.parser.parse_args() if args.command is None: self.parser.print_help() else: try: self.command_dict[args.command](args) except InitializationError: print( "Git gud has not been initialized. Initialize using \"git gud start\"" ) pass
class GitGud: def __init__(self): self.file_operator = get_operator() # Only gets operator if Git Gud has been initialized self.parser = argparse.ArgumentParser(prog='git gud') load_description = '\n'.join([ "Load a specific skill or level. This command can be used in several ways.", "\n", "============Basic Usage============", "\n", "These commands are the simplest commands to load a level on a certain skill, and are identical in functionality:", "\n", " git gud load <skill> <level>", " git gud load <skill>-<level>", "\n", "<skill> and <level> could either be the name of the skill/level or the number of the skill/level.", "Running `git gud skills` will help you find the number and name associated with each skill/level.", "\n", "Here are example uses which load the same level:", "\n", " git gud load basics-2", " git gud load 1 branching", "\n", "============Additional Commands============", "\n", "`git gud load` supports additional shortcut commands to ease level navigation.", "\n", "======Loading the first level on a skill======", "\n", "This command loads the first level on the specified skill:", "\n", " git gud load <skill>", "\n", "======Loading a level on the current skill======", "\n", "This command loads the specified level of the current skill.", "NOTE: <level> MUST be a number in order for this command to work.", "\n", " git gud load -<level>", "\n", ]) self.subparsers = self.parser.add_subparsers(title='Subcommands', metavar='<command>', dest='command') help_parser = self.subparsers.add_parser('help', help='Show help for commands', description='Show help for commands') init_parser = self.subparsers.add_parser('init', help='Init Git Gud and load first level', description='Initialize the direcotry with a git repository and load the first level of Git Gud.') status_parser = self.subparsers.add_parser('status', help='Print out the name of the current level', description='Print out the name of the current level') instructions_parser = self.subparsers.add_parser('instructions', help='Show the instructions for the current level', description='Show the instructions for the current level') goal_parser = self.subparsers.add_parser('goal', help='Concisely show what needs to be done to complete the level.', description='Concisely show what needs to be done to complete the level.') reset_parser = self.subparsers.add_parser('reset', help='Reset the current level', description='Reset the current level') reload_parser = self.subparsers.add_parser('reload', help='Alias for reset', description='Reset the current level. Reload command is an alias for reset command.') test_parser = self.subparsers.add_parser('test', help="Test to see if you've successfully completed the current level", description="Test to see if you've successfully completed the current level") skills_parser = self.subparsers.add_parser('skills', help='List skills', description='List skills') levels_parser = self.subparsers.add_parser('levels', help='List levels in a skill', description='List the levels in the specified skill or in the current skill if Git Gud has been initialized and no skill is provided.') load_parser = self.subparsers.add_parser('load', help='Load a specific skill or level', description=load_description, formatter_class=argparse.RawDescriptionHelpFormatter) commit_parser = self.subparsers.add_parser('commit', help='Quickly create and commit a file', description='Quickly create and commit a file') goal_parser = self.subparsers.add_parser('goal', help='Show a description of the current goal', description='Show a description of the current goal') show_tree_parser = self.subparsers.add_parser('show-tree', help='Show the current state of the branching tree', description='Show the current state of the branching tree') contrib_parser = self.subparsers.add_parser('contributors', help='Show project contributors webpage', description='Show all the contributors of the project') issues_parser = self.subparsers.add_parser('issues', help='Show project issues webpage', description="Show all the issues for the project") help_parser.add_argument('command_name', metavar='<command>', nargs='?') init_parser.add_argument('--force', action='store_true') levels_parser.add_argument('skill_name', metavar='skill', nargs='?') load_parser.add_argument('skill_name', metavar='skill', help='Skill to load') load_parser.add_argument('level_name', metavar='level', nargs='?', help='Level to load') commit_parser.add_argument('file', nargs='?') self.command_dict = { 'help': self.handle_help, 'init': self.handle_init, 'status': self.handle_status, 'instructions': self.handle_instructions, 'goal': self.handle_goal, 'reset': self.handle_reset, 'reload': self.handle_reset, 'test': self.handle_test, 'skills': self.handle_skills, 'levels': self.handle_levels, 'load': self.handle_load, 'commit': self.handle_commit, 'show-tree': self.handle_show_tree, 'contributors': self.handle_contrib, 'issues': self.handle_issues } def is_initialized(self): return self.file_operator is not None def assert_initialized(self, skip_level_check=False): if not self.is_initialized(): raise InitializationError('Git gud has not been initialized. Use "git gud init" to initialize') if not skip_level_check: try: self.file_operator.get_level() except KeyError: level_name = self.file_operator.read_level_file() raise InitializationError('Currently loaded level does not exist: "{}"'.format(level_name)) def load_level(self, level): level.setup(self.file_operator) self.file_operator.write_level(level) show_tree() def handle_help(self, args): if args.command_name is None: self.parser.print_help() else: try: self.subparsers.choices[args.command_name].print_help() except KeyError: print('No such command exists: "{}"\n'.format(args.command_name)) self.parser.print_help() def handle_init(self, args): # Make sure it's safe to initialize if not args.force: # We aren't forcing if self.file_operator: print('Repo {} already initialized for git gud.'.format(self.file_operator.path)) print('Use --force to initialize {}.'.format(os.getcwd())) return self.file_operator = Operator(os.getcwd(), initialize_repo=False) if os.path.exists(self.file_operator.git_path): # Current directory is a git repo print('Currently in a git repo. Use --force to force initialize here.') return if os.path.exists(self.file_operator.gg_path): # Current directory is a git repo print('Git gud has already initialized. Use --force to force initialize again.') return if len(os.listdir(self.file_operator.path)) != 0: print('Current directory is nonempty. Use --force to force initialize here.') return else: print('Force initializing git gud.') if not self.file_operator: self.file_operator = Operator(os.getcwd(), initialize_repo=False) # After here, we initialize everything try: self.file_operator.repo = Repo(self.file_operator.path) except InvalidGitRepositoryError: self.file_operator.repo = Repo.init(self.file_operator.path) if not os.path.exists(self.file_operator.gg_path): os.mkdir(self.file_operator.gg_path) python_exec = sys.executable.replace('\\', '/') # Git uses unix-like path separators for git_hook_name, module_hook_name in all_hooks: path = os.path.join(self.file_operator.hooks_path, git_hook_name) with open(path, 'w+') as hook_file: hook_file.write('#!/bin/sh' + os.linesep) hook_file.write('' + python_exec + ' -m gitgud.hooks.' + module_hook_name + " $@" + os.linesep) hook_file.write( "if [[ $? -ne 0 ]]" + os.linesep + "" \ "then" + os.linesep + "" \ "\t exit 1" + os.linesep+ "" \ "fi" + os.linesep) # Make the files executable mode = os.stat(path).st_mode mode |= (mode & 0o444) >> 2 os.chmod(path, mode) print('Git Gud successfully setup in {}'.format(os.getcwd())) print('Welcome to Git Gud!') print() self.load_level(all_skills["1"]["1"]) def handle_status(self, args): if self.is_initialized(): try: level = self.file_operator.get_level() print('Currently on level: "{}"'.format(level.full_name())) except KeyError: level_name = self.file_operator.read_level_file() print('Currently on unregistered level: "{}"'.format(level_name)) else: print("Git gud not initialized.") print('Initialize using "git gud init"') def handle_instructions(self, args): self.assert_initialized() self.file_operator.get_level().instructions() def handle_goal(self, args): self.assert_initialized() self.file_operator.get_level().goal() def handle_reset(self, args): self.assert_initialized() level = self.file_operator.get_level() print("Resetting...") level.setup(self.file_operator) show_tree() def handle_test(self, args): self.assert_initialized() level = self.file_operator.get_level() level.test(self.file_operator) def handle_skills(self, args): if self.is_initialized(): try: cur_skill = self.file_operator.get_level().skill print('Currently on skill: "{}"'.format(cur_skill.name)) print() except KeyError: pass skill_chars = max(len(skill.name) for skill in all_skills) skill_format_template = 'Skill {{}} - "{{:<{}}}" :{{:>2}} level{{}}'.format(skill_chars) level_format_template = " Level {:>2} : {:<3}" for i, skill in enumerate(all_skills): # TODO Add description print(skill_format_template.format(i + 1, skill.name, len(skill), ("", "s")[len(skill) > 1])) for index, level in enumerate(skill): print(level_format_template.format(index + 1, level.name)) print("\nLoad a level with `git gud load`") def handle_levels(self, args): key_error_flag = False if args.skill_name is None: if self.file_operator is None: self.subparsers.choices['levels'].print_help() return try: skill = self.file_operator.get_level().skill except KeyError: skill_name = self.file_operator.read_level_file().split()[0] print('Cannot find any levels in skill: "{}"'.format(skill_name)) return else: try: skill = all_skills[args.skill_name] except KeyError: print('There is no skill "{}".'.format(args.skill_name)) print('You may run "git gud skills" to print all the skills. \n') skill = self.file_operator.get_level().skill key_error_flag = True if key_error_flag or args.skill_name is None: print('Levels in the current skill "{}" : \n'.format(skill.name)) else: print('Levels for skill "{}" : \n'.format(skill.name)) for index, level in enumerate(skill): print(str(index + 1) + ": " + level.name) print('\nTo see levels in all skills, run "git gud skills".') def handle_load(self, args): self.assert_initialized(skip_level_check=True) argskillset = args.skill_name.split("-", 1) # Set up args.level_name and args.skill_name if args.level_name: if args.skill_name is "-": # Replace the dash with the current skill's name. args.skill_name = self.file_operator.get_level().skill.name else: if len(argskillset) == 2: args.skill_name, args.level_name = tuple(argskillset) else: args.skill_name, args.level_name = argskillset[0], None skill_to_load = self.file_operator.get_level().skill.name if args.skill_name: if args.skill_name.lower() in {"next", "prev", "previous"}: query = args.skill_name.lower() level = self.file_operator.get_level() if query == "next": level_to_load = level.next_level else: query = "previous" level_to_load = level.prev_level print("Loading the {} level...\n".format(query)) if level_to_load is not None: self.load_level(level_to_load) else: print('To view levels/skills, use "git gud levels" or "git gud skills"\n') if query == "next": print_all_complete() else: print('Already on the first level. To reload the level, use "git gud reload".') return else: skill_to_load = args.skill_name level_to_load = '1' if args.level_name: level_to_load = args.level_name if skill_to_load in all_skills.keys(): skill = all_skills[skill_to_load] if level_to_load in skill.keys(): level = skill[level_to_load] self.load_level(level) else: print('Level "{}" does not exist'.format(args.level_name)) print('To view levels/skills, use "git gud levels" or "git gud skills"\n') else: print('Skill "{}" does not exist'.format(args.skill_name)) print('To view levels/skills, use "git gud levels" or "git gud skills"\n') def handle_commit(self, args): self.assert_initialized() last_commit = self.file_operator.get_last_commit() commit_name = str(int(last_commit) + 1) if args.file is not None: try: int(args.file) commit_name = args.file except ValueError: pass print('Simulating: Create file "{}"'.format(commit_name)) print('Simulating: git add {}'.format(commit_name)) print('Simulating: git commit -m "{}"'.format(commit_name)) commit = self.file_operator.add_and_commit(commit_name) print("New Commit: {}".format(commit.hexsha[:7])) # Check if the newest commit is greater than the last_commit, if yes, then write if int(commit_name) > int(last_commit): self.file_operator.write_last_commit(commit_name) def handle_show_tree(self, args): show_tree() def handle_contrib(self, args): contrib_website = "https://github.com/benthayer/git-gud/graphs/contributors" webbrowser.open_new(contrib_website) def handle_issues(self, args): issues_website = "https://github.com/benthayer/git-gud/issues" webbrowser.open_new(issues_website) def parse(self): args, _ = self.parser.parse_known_args() if args.command is None: self.parser.print_help() else: try: self.command_dict[args.command](args) except InitializationError as error: print(error)
class GitGud: def __init__(self): self.file_operator = get_operator( ) # Only gets operator if in a valid gitgud repo self.parser = argparse.ArgumentParser(prog='git gud') self.subparsers = self.parser.add_subparsers(title='Subcommands', metavar='<command>', dest='command') # TODO Add git gud help <command>, which would return the same output as git gud <command> -- help # TODO Display help message for subcommand when it fails. # ie `git gud load level1 challenge1 random-input` should have output similar to `git gud load --help` # Use "git gud help" to print helps of all subcommands. # "git gud help <command>" prints the description of the <command> but not help. # TODO Add longer descriptions help_parser = self.subparsers.add_parser( 'help', help='Show help for commands', description='Show help for commands') start_parser = self.subparsers.add_parser('start', help='Git started!', description='Git started!') status_parser = self.subparsers.add_parser( 'status', help='Print out the name of the current challenge', description='Print out the name of the current challenge') instructions_parser = self.subparsers.add_parser( 'instructions', help='Show the instructions for the current challenge', description='Show the instructions for the current challenge') goal_parser = self.subparsers.add_parser( 'goal', help= 'Concisely show what needs to be done to complete the challenge.', description= 'Concisely show what needs to be done to complete the challenge.') reset_parser = self.subparsers.add_parser( 'reset', help='Reset the current challenge', description='Reset the current challenge') reload_parser = self.subparsers.add_parser( 'reload', help= 'Reset the current challenge. Reload command is an alias for reset command.', description= 'Reset the current challenge. Reload command is an alias for reset command.' ) test_parser = self.subparsers.add_parser( 'test', help= 'Test to see if you\'ve successfully completed the current challenge', description= 'Test to see if you\'ve successfully completed the current challenge' ) progress_parser = self.subparsers.add_parser( 'progress', help='Continue to the next challenge', description='Continue to the next challenge') levels_parser = self.subparsers.add_parser('levels', help='List levels', description='List levels') challenges_parser = self.subparsers.add_parser( 'challenges', help='List challenges', description= 'List challenges in current level or in other level if specified') load_parser = self.subparsers.add_parser( 'load', help='Load a specific level or challenge', description='Load a specific level or challenge') commit_parser = self.subparsers.add_parser( 'commit', help='Quickly create and commit a file', description='Quickly create and commit a file') goal_parser = self.subparsers.add_parser( 'goal', help='Show a description of the current goal', description='Show a description of the current goal') show_tree_parser = self.subparsers.add_parser( 'show-tree', help='Show the current state of the branching tree', description='Show the current state of the branching tree') contrib_parser = self.subparsers.add_parser( 'contributors', help='Show all the contributors of the project', description='Show all the contributors of the project') help_parser.add_argument('command_name', metavar='<command>', nargs='?') start_parser.add_argument('--force', action='store_true') challenges_parser.add_argument('level_name', metavar='level', nargs='?') load_parser.add_argument('level_name', metavar='level', help='Level to load') load_parser.add_argument('challenge_name', metavar='challenge', nargs='?', help='Challenge to load') commit_parser.add_argument('file', nargs='?') self.command_dict = { 'help': self.handle_help, 'start': self.handle_start, 'status': self.handle_status, 'instructions': self.handle_instructions, 'goal': self.handle_goal, 'reset': self.handle_reset, 'reload': self.handle_reset, 'test': self.handle_test, 'progress': self.handle_progress, 'levels': self.handle_levels, 'challenges': self.handle_challenges, 'load': self.handle_load, 'commit': self.handle_commit, 'show-tree': self.handle_show_tree, 'contributors': self.handle_contrib, } def is_initialized(self): return self.file_operator is not None def assert_initialized(self): if not self.is_initialized(): raise InitializationError( "Git gud not initialized. Use \"git gud start\" to initialize") def load_challenge(self, challenge): challenge.setup(self.file_operator) self.file_operator.write_challenge(challenge) show_tree() def handle_help(self, args): if args.command_name is None: self.parser.print_help() else: try: self.subparsers.choices[args.command_name].print_help() except KeyError: print('No such command exists: \"{}\"\n'.format( args.command_name)) self.parser.print_help() def handle_start(self, args): # Make sure it's safe to initialize if not args.force: # We aren't forcing if self.file_operator: print('Repo {} already initialized for git gud.'.format( self.file_operator.path)) print('Use --force to initialize {}.'.format(os.getcwd())) return self.file_operator = Operator(os.getcwd(), initialize_repo=False) if os.path.exists(self.file_operator.git_path): # Current directory is a git repo print( 'Currently in a git repo. Use --force to force initialize here.' ) return if os.path.exists(self.file_operator.gg_path): # Current directory is a git repo print( 'Git gud has already initialized. Use --force to force initialize again.' ) return if len(os.listdir(self.file_operator.path)) != 0: print( 'Current directory is nonempty. Use --force to force initialize here.' ) return else: print('Force initializing git gud.') if not self.file_operator: self.file_operator = Operator(os.getcwd(), initialize_repo=False) # After here, we initialize everything try: self.file_operator.repo = Repo(self.file_operator.path) except InvalidGitRepositoryError: self.file_operator.repo = Repo.init(self.file_operator.path) if not os.path.exists(self.file_operator.gg_path): os.mkdir(self.file_operator.gg_path) with open(self.file_operator.last_commit_path, 'w+') as commit_file: commit_file.write('0') # First commit will be 1 with open(self.file_operator.level_path, 'w+') as level_file: level_file.write(all_levels[0][0].full_name()) python_exec = sys.executable.replace( '\\', '/') # Git uses unix-like path separators for git_hook_name, module_hook_name in all_hooks: with open( os.path.join(self.file_operator.hooks_path, git_hook_name), 'w+') as hook_file: hook_file.write('#!/bin/sh' + os.linesep) hook_file.write('cat - | ' + python_exec + ' -m gitgud.hooks.' + module_hook_name + ' "$@"' + os.linesep) hook_file.write('exit 0' + os.linesep) print('Git Gud successfully setup in {}'.format(os.getcwd())) self.file_operator.get_challenge().setup(self.file_operator) show_tree() def handle_status(self, args): if self.is_initialized(): challenge_name = self.file_operator.get_challenge().full_name() print("Currently on challenge: \"{}\"".format(challenge_name)) else: print("Git gud not initialized.") print("Initialize using \"git gud start\"") def handle_instructions(self, args): self.assert_initialized() self.file_operator.get_challenge().instructions() def handle_goal(self, args): self.assert_initialized() self.file_operator.get_challenge().goal() def handle_reset(self, args): self.assert_initialized() challenge = self.file_operator.get_challenge() print("Resetting...") challenge.setup(self.file_operator) show_tree() def handle_test(self, args): self.assert_initialized() challenge = self.file_operator.get_challenge() if challenge.test(self.file_operator): try: if challenge.next_challenge.level != challenge.level: print( "Challenge complete, you've completed this level! `git gud progress` to advance to the next level" ) print("Next level is: {}".format( challenge.next_challenge.level.name)) else: print( "Challenge complete! `git gud progress` to advance to the next challenge" ) print("Next challenge is: {}".format( challenge.next_challenge.full_name())) except AttributeError: print("All challenges completed!") else: print( "Challenge not complete, keep trying. `git gud reset` to start from scratch." ) def handle_progress(self, args): self.assert_initialized() print("Progressing to next level...") challenge = self.file_operator.get_challenge() next_challenge = challenge.next_challenge if next_challenge is not None: self.load_challenge(next_challenge) else: print("Wow! You've complete every challenge, congratulations!") print( "If you want to keep learning git, why not try contributing to git-gud by forking us at https://github.com/bthayer2365/git-gud/" ) print( "We're always looking for contributions and are more than happy to accept both pull requests and suggestions!" ) def handle_levels(self, args): cur_level = self.file_operator.get_challenge().level print("Currently on level: \"{}\"\n".format(cur_level.name)) for level in all_levels: # TODO Add description # 10 characters for the short IDs. print("Level {:<10} :{:>2} challenge{}".format( "\"" + level.name + "\"", len(level), ("", "s")[len(level) > 1])) for index, challenge in enumerate(level): # " " * (characters allocated for ID - 6) print("{}Challenge {:>2} : {:<10}".format( " " * 4, index + 1, challenge.name)) def handle_challenges(self, args): key_error_flag = False if args.level_name is None: level = self.file_operator.get_challenge().level else: try: level = all_levels[args.level_name] except KeyError: print("There is no level \"{}\".".format(args.level_name)) print( "You may run \"git gud levels\" to print all the levels. \n" ) level = self.file_operator.get_challenge().level key_error_flag = True if key_error_flag == True or args.level_name is None: print("Challenges in the current level \"{}\" : \n".format( level.name)) else: print("Challenges for level \"{}\" : \n".format(level.name)) for index, challenge in enumerate(level): print(str(index + 1) + ": " + challenge.name) def handle_load(self, args): self.assert_initialized() if args.level_name in all_levels: level = all_levels[args.level_name] if args.challenge_name is not None: if args.challenge_name in all_levels[args.level_name]: challenge = level[args.challenge_name] self.load_challenge(challenge) else: print("Challenge \"{}\" does not exist".format( args.challenge_name)) print( "To view challenges/levels, use git gud challenges or git gud levels" ) else: self.load_challenge(level[0]) else: print("Level \"{}\" does not exist".format(args.level_name)) print( "To view challenges/levels, use git gud challenges or git gud levels" ) def handle_commit(self, args): self.assert_initialized() last_commit = self.file_operator.get_last_commit() commit_name = str(int(last_commit) + 1) if args.file is not None: try: int(args.file) commit_name = args.file except ValueError: pass print("Simulating: Create file \"{}\"".format(commit_name)) print("Simulating: git add {}".format(commit_name)) print("Simulating: git commit -m \"{}\"".format(commit_name)) commit = self.file_operator.add_and_commit(commit_name) print("New Commit: {}".format(commit.hexsha[:7])) # Check if the newest commit is greater than the last_commit, if yes, then write if int(commit_name) > int(last_commit): self.file_operator.write_last_commit(commit_name) def handle_show_tree(self, args): show_tree() def handle_contrib(self, args): contrib_website = "https://github.com/bthayer2365/git-gud/graphs/contributors" webbrowser.open_new(contrib_website) def parse(self): args, _ = self.parser.parse_known_args() if args.command is None: self.parser.print_help() else: try: self.command_dict[args.command](args) except InitializationError: print( "Git gud has not been initialized. Initialize using \"git gud start\"" ) pass
class GitGud: def __init__(self): self.file_operator = get_operator( ) # Only gets operator if Git Gud has been initialized self.parser = argparse.ArgumentParser(prog='git gud') self.subparsers = self.parser.add_subparsers(title='Subcommands', metavar='<command>', dest='command') help_parser = self.subparsers.add_parser( 'help', help='Show help for commands', description='Show help for commands') init_parser = self.subparsers.add_parser( 'init', help='Init Git Gud and load first level', description= 'Initialize the direcotry with a git repository and load the first level of Git Gud.' ) status_parser = self.subparsers.add_parser( 'status', help='Print out the name of the current level', description='Print out the name of the current level') instructions_parser = self.subparsers.add_parser( 'instructions', help='Show the instructions for the current level', description='Show the instructions for the current level') goal_parser = self.subparsers.add_parser( 'goal', help='Concisely show what needs to be done to complete the level.', description= 'Concisely show what needs to be done to complete the level.') reset_parser = self.subparsers.add_parser( 'reset', help='Reset the current level', description='Reset the current level') reload_parser = self.subparsers.add_parser( 'reload', help='Alias for reset', description= 'Reset the current level. Reload command is an alias for reset command.' ) test_parser = self.subparsers.add_parser( 'test', help= "Test to see if you've successfully completed the current level", description= "Test to see if you've successfully completed the current level") progress_parser = self.subparsers.add_parser( 'progress', help='Continue to the next level', description='Continue to the next level') skills_parser = self.subparsers.add_parser('skills', help='List skills', description='List skills') levels_parser = self.subparsers.add_parser( 'levels', help='List levels in a skill', description= 'List the levels in the specified skill or in the current skill if Git Gud has been initialized and no skill is provided.' ) load_parser = self.subparsers.add_parser( 'load', help='Load a specific skill or level', description='Load a specific skill or level') commit_parser = self.subparsers.add_parser( 'commit', help='Quickly create and commit a file', description='Quickly create and commit a file') goal_parser = self.subparsers.add_parser( 'goal', help='Show a description of the current goal', description='Show a description of the current goal') show_tree_parser = self.subparsers.add_parser( 'show-tree', help='Show the current state of the branching tree', description='Show the current state of the branching tree') contrib_parser = self.subparsers.add_parser( 'contributors', help='Show project contributors webpage', description='Show all the contributors of the project') help_parser.add_argument('command_name', metavar='<command>', nargs='?') init_parser.add_argument('--force', action='store_true') levels_parser.add_argument('skill_name', metavar='skill', nargs='?') load_parser.add_argument('skill_name', metavar='skill', help='Skill to load') load_parser.add_argument('level_name', metavar='level', nargs='?', help='Level to load') commit_parser.add_argument('file', nargs='?') self.command_dict = { 'help': self.handle_help, 'init': self.handle_init, 'status': self.handle_status, 'instructions': self.handle_instructions, 'goal': self.handle_goal, 'reset': self.handle_reset, 'reload': self.handle_reset, 'test': self.handle_test, 'progress': self.handle_progress, 'skills': self.handle_skills, 'levels': self.handle_levels, 'load': self.handle_load, 'commit': self.handle_commit, 'show-tree': self.handle_show_tree, 'contributors': self.handle_contrib, } def is_initialized(self): return self.file_operator is not None def assert_initialized(self, skip_level_check=False): if not self.is_initialized(): raise InitializationError( 'Git gud has not been initialized. Use "git gud init" to initialize' ) if not skip_level_check: try: self.file_operator.get_level() except KeyError: level_name = self.file_operator.read_level_file() raise InitializationError( 'Currently loaded level does not exist: "{}"'.format( level_name)) def load_level(self, level): level.setup(self.file_operator) self.file_operator.write_level(level) show_tree() def handle_help(self, args): if args.command_name is None: self.parser.print_help() else: try: self.subparsers.choices[args.command_name].print_help() except KeyError: print('No such command exists: "{}"\n'.format( args.command_name)) self.parser.print_help() def handle_init(self, args): # Make sure it's safe to initialize if not args.force: # We aren't forcing if self.file_operator: print('Repo {} already initialized for git gud.'.format( self.file_operator.path)) print('Use --force to initialize {}.'.format(os.getcwd())) return self.file_operator = Operator(os.getcwd(), initialize_repo=False) if os.path.exists(self.file_operator.git_path): # Current directory is a git repo print( 'Currently in a git repo. Use --force to force initialize here.' ) return if os.path.exists(self.file_operator.gg_path): # Current directory is a git repo print( 'Git gud has already initialized. Use --force to force initialize again.' ) return if len(os.listdir(self.file_operator.path)) != 0: print( 'Current directory is nonempty. Use --force to force initialize here.' ) return else: print('Force initializing git gud.') if not self.file_operator: self.file_operator = Operator(os.getcwd(), initialize_repo=False) # After here, we initialize everything try: self.file_operator.repo = Repo(self.file_operator.path) except InvalidGitRepositoryError: self.file_operator.repo = Repo.init(self.file_operator.path) if not os.path.exists(self.file_operator.gg_path): os.mkdir(self.file_operator.gg_path) with open(self.file_operator.last_commit_path, 'w+') as commit_file: commit_file.write('0') # First commit will be 1 with open(self.file_operator.level_path, 'w+') as level_file: level_file.write(all_skills[0][0].full_name()) python_exec = sys.executable.replace( '\\', '/') # Git uses unix-like path separators for git_hook_name, module_hook_name in all_hooks: with open( os.path.join(self.file_operator.hooks_path, git_hook_name), 'w+') as hook_file: hook_file.write('#!/bin/sh' + os.linesep) hook_file.write('cat - | ' + python_exec + ' -m gitgud.hooks.' + module_hook_name + ' "$@"' + os.linesep) hook_file.write('exit 0' + os.linesep) print('Git Gud successfully setup in {}'.format(os.getcwd())) print('Welcome to Git Gud!') print() self.file_operator.get_level().setup(self.file_operator) show_tree() def handle_status(self, args): if self.is_initialized(): try: level = self.file_operator.get_level() print('Currently on level: "{}"'.format(level.full_name())) except KeyError: level_name = self.file_operator.read_level_file() print( 'Currently on unregistered level: "{}"'.format(level_name)) else: print("Git gud not initialized.") print('Initialize using "git gud init"') def handle_instructions(self, args): self.assert_initialized() self.file_operator.get_level().instructions() def handle_goal(self, args): self.assert_initialized() self.file_operator.get_level().goal() def handle_reset(self, args): self.assert_initialized() level = self.file_operator.get_level() print("Resetting...") level.setup(self.file_operator) show_tree() def handle_test(self, args): self.assert_initialized() level = self.file_operator.get_level() level.test(self.file_operator) def handle_progress(self, args): self.assert_initialized() print("Progressing to next skill...") print() level = self.file_operator.get_level() next_level = level.next_level if next_level is not None: self.load_level(next_level) else: print_all_complete() def handle_skills(self, args): if self.is_initialized(): try: cur_skill = self.file_operator.get_level().skill print('Currently on skill: "{}"'.format(cur_skill.name)) print() except KeyError: pass for skill in all_skills: # TODO Add description # 10 characters for the short IDs. print("Skill {:<10} :{:>2} level{}".format( "\"" + skill.name + "\"", len(skill), ("", "s")[len(skill) > 1])) for index, level in enumerate(skill): # " " * (characters allocated for ID - 6) print("{}Level {:>2} : {:<10}".format(" " * 4, index + 1, level.name)) def handle_levels(self, args): key_error_flag = False if args.skill_name is None: if self.file_operator is None: self.subparsers.choices['levels'].print_help() return try: skill = self.file_operator.get_level().skill except KeyError: skill_name = self.file_operator.read_level_file().split()[0] print( 'Cannot find any levels in skill: "{}"'.format(skill_name)) return else: try: skill = all_skills[args.skill_name] except KeyError: print('There is no skill "{}".'.format(args.skill_name)) print( 'You may run "git gud skills" to print all the skills. \n') skill = self.file_operator.get_level().skill key_error_flag = True if key_error_flag or args.skill_name is None: print('Levels in the current skill "{}" : \n'.format(skill.name)) else: print('Levels for skill "{}" : \n'.format(skill.name)) for index, level in enumerate(skill): print(str(index + 1) + ": " + level.name) def handle_load(self, args): self.assert_initialized(skip_level_check=True) if args.skill_name in all_skills: skill = all_skills[args.skill_name] if args.level_name is not None: if args.level_name in all_skills[args.skill_name]: level = skill[args.level_name] self.load_level(level) else: print('Level "{}" does not exist'.format(args.level_name)) print( "To view levels/skills, use git gud levels or git gud skills" ) else: self.load_level(skill[0]) else: print('Skill "{}" does not exist'.format(args.skill_name)) print( "To view levels/skills, use git gud levels or git gud skills") def handle_commit(self, args): self.assert_initialized() last_commit = self.file_operator.get_last_commit() commit_name = str(int(last_commit) + 1) if args.file is not None: try: int(args.file) commit_name = args.file except ValueError: pass print('Simulating: Create file "{}"'.format(commit_name)) print('Simulating: git add {}'.format(commit_name)) print('Simulating: git commit -m "{}"'.format(commit_name)) commit = self.file_operator.add_and_commit(commit_name) print("New Commit: {}".format(commit.hexsha[:7])) # Check if the newest commit is greater than the last_commit, if yes, then write if int(commit_name) > int(last_commit): self.file_operator.write_last_commit(commit_name) def handle_show_tree(self, args): show_tree() def handle_contrib(self, args): contrib_website = "https://github.com/benthayer/git-gud/graphs/contributors" webbrowser.open_new(contrib_website) def parse(self): args, _ = self.parser.parse_known_args() if args.command is None: self.parser.print_help() else: try: self.command_dict[args.command](args) except InitializationError as error: print(error)