× К оглавлению На главную Об авторе

Дата и время публикации:    

Проблемы и решение

1. Суть проблемы

Выпущенная версия python-shellenv-1.5, в конце марта 2022 года, имела один недостаток — отсутствие аргументов командной строки, что приводило к печати на консоль вывода, полученного в результате выполения подмодуля shellenv.testrun:functionality_check(), как показано в дампе 1.1

Дамп 1.1

(venv-shellenv) ~/.../venv-shellenv/src/python-shellenv/$ bin/py-shellenv
Script's name: testrun.py
Call print_all() from module shellenv:
	*** Print all environment variables ***
PASSED
Call nmaxhitsenv()/nminhitsenv() from module shellenv:
	*** Scans for hits ***
	SHELL:0 may be a maximum compliance with: SHERRYHW_NAME
	SESSION_MANAGER:1 may be a minimum compliance with: SHERRYHW_NAME
PASSED
Call setenv('SHERRYHW_NAME') from module shellenv:
PASSED
Call run_unsetenv('MYVAR' and 'SHERRYHW_NAME') from module shellenv:
	 *** test to unsetenv() when envar does not exists ***
	 Sure, MYVAR's does not exists - it's right
	 *** test to unsetenv() test to unsetenv() to removes SHERRYHW_NAME ***
	 It was not unexpected that SHERRYHW_NAME had the value 'Sherry platform'
PASSED
Call dotenv from module shellenv:
*** test to read file [${HOME}/.config/shellenv/].env  ***
Create tested directory: /.config/shellenv/
Create tested dotfile: /home/user/.config/shellenv/.env
Try to read recently recorded envars:
SHELLENV_DIR: /.config/shellenv/
DOMAIN: localhost
USER_EMAIL: user@localhost
PASSED

2. Решение

2.1 Модуль разбора комадной строки argparse

Первое, что мне понадобилось — это видоизменить точку входа в модуль shellenv.__main__:main для разбора командной строки, как показано в листинге 2.1

Листинге 2.1

 ...
import typing as t
import argparse
 ...

def main(argv: t.Sequence[str] = None) -> None:
 ...
parser = argparse.ArgumentParser(prog='shellenv', usage='%(prog)s [options]')
 ...

Разбор командной строки осуществляется с использованием модуля argparse, который предоставляет большую функциональность чем давно существующие модули из стандартной библиотеки, такие как getopt и optparse [3.3]. Кроме того, этот модуль поддерживает не только опции позиционные аргументы, необязательные и обязательные опции, а так же опции с иным синтаксисом чем в *nix-системах, наподобие “/f” и “+rgb” , а так же подкоманды и многие другие опции [3.4]. При этом, в своем модуле shellenv решил придерживаться стилистики представления опций в командной строке, как приянто в *nix-системах.

2.2 Определение необязательных опций

К необязательным опциям, в моем случае, относится встроенная опция -h, --help и объявленная -V, --version, как показано в листинге 2.2.1

Листинге 2.2.1

 ...
 # Unessential option                   
 parser.add_argument(
        "-V","--version",
        action="vprog",
        version=str('The %(prog)s'+'-'+__version__),
        help=str("returns help page and exit immediate")
 )
 #These also include help options which built into ArgumentParser
 ...

Как показано в листинге 2.2, присвоенное строковое значение "version" атрибуту action является одним из установленных действий, которому устанавливается базовый класс [3.7], выбираемый с помощью 'store_const', 'store_true', 'store_false', 'append', 'append_const', 'count', 'help', 'version', 'parsers' [3.6].

2.3 Разбор аргументов командной строки

В версии модуля shellenv-1.6 был добавлен подмодуль cmdlineargs пакета python-shellenv, в котором была внесена функция shellenv.cmdlineargs:mloop_exec() , в котором во время разбора аргументов командной строки производится запуск обработчиков, назначенных атрибуту action [3.5] и образованных от базового класса argparse.Action и порожденного от него класса _CommonAction, как показано в листинге 2.3.1

Листинге 2.3.1

class _CommonAction(argparse.Action):
      """
          Defining the Common Action to set an option handler 
      """
      def __init__(self, catcher, **kwargs ) :      
             ...
...
class _TestRunAction(_CommonAction):
      """
          Defining Action for option --test
      """
       def __init__(self, option_strings, dest, nargs=None, **kwargs ):
             ...
      @classmethod
      def catcher(cls, **kwargs ):
             ...
...
class _OutputAllRunAction(_CommonAction):
      """
         Defining Action for option --output-all
      """
      def __init__(self, option_strings, dest, nargs=None, **kwargs ):
             ...
          @classmethod
      def catcher(cls, **kwargs ):
             ...
...
class _GetenvAllRunAction(_CommonAction):
      """
         Defining Action for option --getenv vkey 
      """
      def __init__(self, option_strings, dest, nargs=None, **kwargs ):      
            ...
           @classmethod
      def catcher(cls, **kwargs ):
            ...

При этом, как можно заметить, что конструктор в базовом классе _CommonAction() принимает переменное число параметров в **kwargs и метод обратного вызова, который будет исполняться в методе catcher в производных классах _TestRunAction(), _OutputAllRunAction(), _GetenvAllRunAction(), как показано в дампе 2.3.1

Вышеперечисленные производные классы позволяют засчет сериализации следовать принятому в Python принципу неповторяемости кода (Don't Repeat Yourself, DRY), который заключается в устранении повторяемости кода при назначении класса, образующий объект действия и которое должно соответствовать следующему образцу, как показано в 2.3.2

Листинге 2.3.2

         
...
Сlass _MyAction(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs ):
          super(_MyAction, self).__init__( option_strings=option_strings, dest=dest, nargs=nargs ,**kwargs )

 def __call__(self, parser, namespace, values, option_string=None):
          print('Run option: namespace=%r, values=%r, option_string=%r' % (namespace, values, option_string))
          setattr(namespace, self.dest, values)
          ...

В конструкторе класса _MyAction:__init__() непосредственно осуществляется вызов к конструктору базового класса argparse.Action:__init__() через встроенную функцию super() в CPython

При этом,

Функция parser.add_argument() принимает и передает на конструктор класса _MyAction:__init__() следующие параметры, которые были использованы в модуле python-shellenv, в настоящей версии:

В настоящем примере для функции mloop_exec(), аргументы командной строки [3.1] могут быть поданы в единственном параметре argv или беруться из sys.argv() [3.2], как показано в листинге 2.3.3

Листинге 2.3.3

       
def mloop_exec(self,argv: t.Sequence[str] = None)→None:
      ...
     if argv is None:
         # argv[0] is the script name so we may be skip it and start from 
         # first item  
         argv = sys.argv[1:]
 parser = argparse.ArgumentParser(prog='shellenv', usage='%(prog)s [options]')
    # Unessential option                   
    parser.add_argument(
                     "-V","--version",
                     action="version",
                     version=str('The %(prog)s'+'-'+__version__),
                     help=str("returns help page and exit immediate")
    )
    parser.add_argument(
                     "-t","--test",
                     required=False,
                     help=str("tests all methods for functionality checking"),
                     action=_TestRunAction 
    )
    parser.add_argument(
                     "-O","--output-all",
                     required=False,
                     help=str("output all of shell enviroment variables"),
                     action=_OutputAllRunAction
    )
    parser.add_argument("--getenv",
                        nargs=1,
                        required=False,
                        help=str("""finds and prints all shell variables on its keyword
                                    or only one variable when it has matched vkey"""),
                        action=_GetenvAllRunAction 
    )

args = parser.parse_args(argv)
parser.print_help()
sys.exit(1)

После установки обработчиков опциям и аргументам командной строки с использованием parser.add_argument() выполняется разбор с помощью parser.parse_args() . И, если ни одна из опций "--test", "--output-all" или "--getenv" в ней не была обнаружена, соответственно не было выполнено ни одно из действий назначенных в классов _TestRunAction, _OutputAllRunAction и т.д., будет выведена справка parser.print_help() и скрипт завершится с возвращаемым положительным значением.

При этом, в argparse.ArgumentParser() устанавливается имя программы в атрибуте prog= и ее вывод в справки в атрибуте usage через магическую строку '%(prog)s', которая присвоит название выполняемой программы.

3. Библиография

3.1 How to use sys.argv in Python

3.2 Command-line list of the module sys in python

3.3 argparse— Parser for command-line options, arguments and sub-commands

3.4 PEP 389 — argparse — New Command Line Parsing Module

3.5 argparse — attribute Action

3.6 Module argparse.py— line 12226

3.7 Module argparse — line 44