Python命令行之旅:使用argparse实现git命令

枫铃3年前 (2021-07-09)Python291

前言

本文将以我们日常工作中最常见的git命令为例,讲解如何使用argparse库来实现一个真正可用的命令行程序。

  • 本文默认使用 Python 3 作为解释器进行讲解。
  • 若你仍在使用 Python 2,请注意两者之间语法和库的使用差异哦~

git常用命令
大家不妨回忆一下,平时最常使用git子命令都有哪些?

当你写好一段代码或增删一些文件后,会用如下命令查看文件状态:

git status

确认文件状态后,会用如下命令将的一个或多个文件(夹)添加到暂存区:

git add [pathspec [pathspec ...]]

然后使用如下命令提交信息:

git commit -m "your commit message"

最后使用如下命令将提交推送到远程仓库:

git push

我们将使用argparse和gitpython库来实现这4个子命令。

关于gitpython
gitpython [1]是一个和git仓库交互的Python第三方库。我们将借用它的能力来实现真正的git逻辑。

安装:

pip install gitpython

思考
在实现前,我们不妨碍先思考下会用到argparse的某些功能?整个程序的结构是怎样的?

argparse

  • 要实现子命令,那么之前介绍到的嵌套解析器必不可少

  • 当用户键入子命令时,子命令所对应的子解析器需要作出响应,那么需要用到子解析器的set_defaults功能

  • 针对git add [pathspec [pathspec …]],我们需要实现位置参数,而且数量是任意个

  • 针对git commit --message msg或git commit -m msg,我们需要实现选项参数,并且可以长选项,又可短选项

程序结构

  • 命令行程序需要一个cli函数来作为统一的入口,它负责合并解析器,并解析命令行参数

  • 我们还需要四个handle_xxx函数响应对应的子命令

则基本结构如下:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴,
互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
import os
import argparse
from git.cmd import Git


def cli():
    """
    git 命名程序入口
    """
    pass


def handle_status(git, args):
    """
    处理 status 命令
    """
    pass

def handle_add(git, args):
    """
    处理 add 命令
    """
    pass


def handle_commit(git, args):
    """
    处理 -m <msg> 命令
    """
    pass


def handle_push(git, args):
    """
    处理 push 命令
    """
    pass


if __name__ == '__main__':
    cli()

下面我们将一步步地实现我们的git程序。

实现
预先我们在argparse-git.py [2]文件中实现我们的git程序。

构建解析器
我们需要整合一个父解析器,作为程序的根解析器,程序名称指定为git。然后在上面添加子解析器,为后续的子命令的解析做准备:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴,
互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
def cli():
    """
    git 命名程序入口
    """
    parser = argparse.ArgumentParser(prog='git')
    subparsers = parser.add_subparsers(
        title='These are common Git commands used in various situations',
        metavar='command')
add_subparsers中的title和metavar参数主要用于控制台帮助信息,最终的效果如下:

usage: git [-h] command ...

optional arguments:
  -h, --help  show this help message and exit

These are common Git commands used in various situations:
  command
    ...

状态子命令
我们需要在cli函数中添加一个用于解析status命令的子解析器status_parser,并指定其对应的处理函数为handle_status。

def cli():
    ...
    # status
    status_parser = subparsers.add_parser(
        'status',
        help='Show the working tree status')
    status_parser.set_defaults(handle=handle_status)

需要说明的是,在status_parser.set_defaults函数中,能接收任意名称的关键字参数,这个参数值会存放于父解析器解析命令行参数后的变量中。

例如,在此示例程序中,我们为每个子解析器定义了handle,那么args = parser.parse_args()中的args将具有handle属性,我们可以引用不同的子命令,那么这个handle就是不同的响应函数。

定义了status的子解析器后,我们再实现下handle_status即可实现status命令的响应:

def handle_status(git, args):
    """
    处理 status 命令
    """
    cmd = ['git', 'status']
    output = git.execute(cmd)
    print(output)

不尖锐出,我们最后调用了真正的git status来实现,并打印了输出。

可能你会对handle_status的函数签名感到困惑,的这里git状语从句:args的英文怎么传入的呢?这其实是由我们自己控制的,将在本文最后讲解。

添加子命令
同样,我们需要在cli函数中添加一个用于解析add命令的子解析器add_parser,并指定其对应的处理函数为handle_add。

额外要做的是,要在子解析器add_parser上添加一个pathspec位置参数,且其数量是任意的:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴,
互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
def cli():
    ...
    # add
    add_parser = subparsers.add_parser(
        'add',
        help='Add file contents to the index')
    add_parser.add_argument(
        'pathspec',
        help='Files to add content from',
        nargs='*')
    add_parser.set_defaults(handle=handle_add)

然后,就是实现handle_add函数,我们需要用到表示文件路径的args.pathspec:

def handle_add(git, args):
    """
    处理 add 命令
    """
    cmd = ['git', 'add'] + args.pathspec
    output = git.execute(cmd)
    print(output)

提交子命令
同样,我们需要在cli函数中添加一个用于解析commit命令的子解析器commit_parser,并指定其对应的处理函数为handle_commit。

额外要做的是,要在子解析器commit_parser上添加一个-m/ --message选项参数,且要求必填:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴,
互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
def cli():
    ...
    # commit
    commit_parser = subparsers.add_parser(
        'commit',
        help='Record changes to the repository')
    commit_parser.add_argument(
        '--message', '-m',
        help='Use the given <msg> as the commit message',
        metavar='msg',
        required=True)
    commit_parser.set_defaults(handle=handle_commit)

然后,就是实现handle_commit函数,我们需要用到表示提交信息的args.message:

def handle_commit(git, args):
    """
    处理 -m <msg> 命令
    """
    cmd = ['git', 'commit', '-m', args.message]
    output = git.execute(cmd)
    print(output)

推子命令
同样,我们需要在cli函数中添加一个用于解析push命令的子解析器push_parser,并指定其对应的处理函数为handle_push。

它同status子命令的实现方式一致:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴,
互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
def cli():
    ...
    # push
    push_parser = subparsers.add_parser(
      'push',
      help='Update remote refs along with associated objects')
    push_parser.set_defaults(handle=handle_push)

然后,就是实现handle_push函数,和handle_status类似:

def handle_push(git, args):
    cmd = ['git', 'push']
    output = git.execute(cmd)
    print(output)

解析参数
在定义完父子解析器,并添加参数后,我们就需要对参数做解析,从而工作也是实现在cli函数中:

def cli():
    ...
    git = Git(os.getcwd())
    args = parser.parse_args()
    if hasattr(args, 'handle'):
        args.handle(git, args)
    else:
        parser.print_help()
  • 通过git.cmd.Git实例化出git对象,用来和git仓库互动

  • 通过parser.parse_args()解析命令行

  • 通过hasattr(args, ‘handle’)判断是否输入了子命令。

    • 由于每个子解析器都定义了handle,那么如果当用户在命令行不输入任何命令时,args就没有handle属性,那么我们就输出帮助信息

    • 如果用户输入了子命令,那么就调用args.handle,引用git和args对象,进行处理对应命令

至此,我们就实现了一个简单的git命令行,使用python argparse-git.py -h查看帮助如下:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴,
互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
usage: git [-h] command ...

optional arguments:
  -h, --help  show this help message and exit

These are common Git commands used in various situations:
  command
    status    Show the working tree status
    add       Add file contents to the index
    commit    Record changes to the repository
    push      Update remote refs along with associated objects

然后我们就可以愉快地使用亲手打造的git程序啦!

小结

本文简单介绍了日常工作中常用的git命令,然后提出实现它的思路,最终一步步使用argparse和gitpython实现了git程序。是不是很有成就感呢?

你是否想过,argparse的四步曲虽然理解简单,但略微麻烦。有没有更简单的方式?如果我很熟悉命令行帮助语法,我能不能写个帮助字符串就把所有的命令行元信息给定义出来?然后就直接轻松轻松地地获取解析后的参数信息呢?

相关文章

python3中reduce函数的使用

在python3中如果使用reduce需要先导入 from functools import reduce reduce函数,reduce...

python-自定义@修饰符

python-自定义@修饰符

__new__函数 在实例化开始之后,在调用 __init__()方法之前,Python 首先调用 __new__() 方法...

在python中如何比较两个float类型的数据是否相等

奇怪的现象 前几天跟同事聊起来,在计算机内部float比较是很坑爹的事情。比方说,0.1+0.2得到的结果竟然不是0.3? >&...

深入理解python之self

首先明确的是self只有在类的方法中才会有,独立的函数或方法是不必带有self的。self在定义类的方法时是必须有的,虽然在调用时...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。