cmd --- پشتیبانی از مفسرهای فرمان خط‌محور

کد منبع: Lib/cmd.py


کلاس Cmd یک چارچوب ساده برای نوشتن مفسرهای فرمان خط‌محور فراهم می‌کند. این مفسرها اغلب برای مهارهای آزمون، ابزارهای مدیریتی و پیش‌نمونه‌هایی مفید هستند که بعداً در یک رابط پیشرفته‌تر قرار خواهند گرفت.

class cmd.Cmd(completekey='tab', stdin=None, stdout=None)

یک نمونه از Cmd یا نمونه‌ای از یک زیرکلاس، یک چارچوب مفسر خط‌محور است. دلیل موجهی برای نمونه‌سازی خود Cmd وجود ندارد؛ در عوض، این کلاس به‌عنوان ابرکلاسِ کلاس مفسری که خودتان تعریف می‌کنید مفید است، تا متدهای Cmd را به ارث ببرید و متدهای عملیاتی را کپسوله کنید.

آرگومان اختیاری completekey نام کلید تکمیل در readline است؛ پیش‌فرض آن Tab است. اگر completekey برابر None نباشد و readline در دسترس باشد، تکمیل فرمان به‌طور خودکار انجام می‌شود.

مقدار پیش‌فرض، 'tab'، به‌طور ویژه‌ای رفتار می‌شود، به‌طوری‌که به کلید Tab در هر readline.backend اشاره می‌کند. به‌طور مشخص، اگر readline.backend برابر editline باشد، Cmd به‌جای 'tab' از '^I' استفاده می‌کند. توجه داشته باشید که سایر مقادیر این‌گونه رفتار نمی‌شوند و ممکن است فقط با یک بک‌اند خاص کار کنند.

آرگومان‌های اختیاری stdin و stdout اشیای پرونده ورودی و خروجی را مشخص می‌کنند که نمونه Cmd یا نمونه زیرکلاس برای ورودی و خروجی از آن‌ها استفاده خواهد کرد. اگر مشخص نشده باشند، مقادیر پیش‌فرض آن‌ها sys.stdin و sys.stdout خواهند بود.

اگر می‌خواهید از یک stdin داده‌شده استفاده شود، حتماً ویژگی use_rawinput نمونه را روی False تنظیم کنید، در غیر این صورت از stdin صرف‌نظر می‌شود.

تغییر یافته در نسخه‌ی 3.13: completekey='tab' برای editline با '^I' جایگزین می‌شود.

اشیای Cmd

نمونه‌ای از Cmd دارای متدهای زیر است:

Cmd.cmdloop(intro=None)

به‌طور مکرر یک اعلان صادر می‌کند، ورودی را می‌پذیرد، یک پیشوند آغازین را از ورودی دریافت‌شده تجزیه می‌کند و متدهای عملیاتی را فراخوانی می‌کند و باقی‌مانده‌ی خط را به‌عنوان آرگومان به آن‌ها می‌فرستد.

آرگومان اختیاری یک رشته بنر یا مقدمه است که پیش از اولین اعلان نمایش داده می‌شود (این مورد، ویژگی کلاس intro را نادیده می‌گیرد).

اگر ماژول readline بارگذاری شده باشد، ورودی به‌طور خودکار ویرایش فهرست تاریخچه‌ی مشابه bash را به ارث می‌برد (برای مثال Control-P به آخرین فرمان بازمی‌گردد، Control-N به فرمان بعدی می‌رود، Control-F مکان‌نما را به سمت راست به‌صورت غیرمخرب حرکت می‌دهد، Control-B مکان‌نما را به سمت چپ به‌صورت غیرمخرب حرکت می‌دهد، و غیره).

پایان پرونده در ورودی به‌عنوان رشته 'EOF' برگردانده می‌شود.

نمونه‌ای از مفسر، نام فرمان foo را اگر و تنها اگر متد do_foo() را داشته باشد، می‌شناسد. در حالتی خاص، سطری که با نویسه '?' آغاز می‌شود، به متد do_help() ارجاع داده می‌شود. در حالت خاص دیگر، سطری که با نویسه '!' آغاز می‌شود، به متد do_shell() ارجاع داده می‌شود (اگر چنین متدی تعریف شده باشد).

این متد زمانی برمی‌گردد که متد postcmd() مقدار درستی را برگرداند. آرگومان stop برای postcmd()، مقدار بازگشتی از متد do_*() متناظر با فرمان است.

اگر تکمیل فعال باشد، تکمیل دستورها به‌صورت خودکار انجام می‌شود و تکمیل آرگومان‌های دستورها با فراخوانی complete_foo() با آرگومان‌های text، line، begidx و endidx انجام می‌شود. text پیشوند رشته‌ای است که سعی در تطبیق آن داریم: همه موارد منطبق برگردانده‌شده باید با آن آغاز شوند. line خط ورودی جاری با حذف فضای سفید ابتدایی است، begidx و endidx اندیس‌های آغاز و پایان متن پیشوند هستند که می‌توان از آن‌ها برای ارائه تکمیل متفاوت بسته به موقعیت آرگومان استفاده کرد.

Cmd.do_help(arg)

همه‌ی زیرکلاس‌های Cmd یک متد do_help() از پیش تعریف‌شده را به ارث می‌برند. وقتی این متد با آرگومان 'bar' فراخوانی شود، متد متناظر help_bar() را فراخوانی می‌کند، و اگر آن متد موجود نباشد، رشته مستند مربوط به do_bar() را در صورت موجود بودن چاپ می‌کند. بدون آرگومان، do_help() همه‌ی موضوعات راهنمای در دسترس را فهرست می‌کند (یعنی همه‌ی فرمان‌هایی که متدهای متناظر help_*() دارند یا فرمان‌هایی که رشته مستندات دارند)، و همچنین فرمان‌های بدون مستند را نیز فهرست می‌کند.

Cmd.onecmd(str)

آرگومان را چنان تفسیر می‌کند که گویی در پاسخ به اعلان تایپ شده است. این را می‌توان بازنویسی کرد، اما معمولاً نیازی به این کار نیست؛ برای قلاب‌های اجرای مفید، متدهای precmd() و postcmd() را ببینید. مقدار بازگشتی، پرچمی است که نشان می‌دهد آیا تفسیر دستورات توسط مفسر باید متوقف شود یا خیر. اگر برای دستور str یک متد do_*() وجود داشته باشد، مقدار بازگشتی آن متد برگردانده می‌شود، در غیر این صورت مقدار بازگشتی از متد default() برگردانده می‌شود.

Cmd.emptyline()

متدی که هنگام وارد شدن یک خط خالی در پاسخ به اعلان فراخوانی می‌شود. اگر این متد بازنویسی نشده باشد، آخرین فرمان غیرخالی واردشده را تکرار می‌کند.

Cmd.default(line)

متدی که وقتی پیشوند فرمان شناسایی نشود، بر روی یک خط ورودی فراخوانی می‌شود. اگر این متد بازنویسی نشود، یک پیام خطا چاپ می‌کند و بازمی‌گردد.

Cmd.completedefault(text, line, begidx, endidx)

متدی که برای تکمیل یک خط ورودی فراخوانی می‌شود، هنگامی که هیچ متد مخصوص دستور complete_*() در دسترس نباشد. به‌طور پیش‌فرض، یک فهرست خالی برمی‌گرداند.

Cmd.columnize(list, displaywidth=80)

متدی که فراخوانی می‌شود تا فهرستی از رشته‌ها را به‌صورت مجموعه‌ای فشرده از ستون‌ها نمایش دهد. هر ستون تنها به اندازه‌ی ضرورت عرض دارد. ستون‌ها برای خوانایی با دو فاصله از هم جدا می‌شوند.

Cmd.precmd(line)

متد قلابکه درست پیش از تفسیر خط فرمان line، اما پس از تولید و صدور اعلان ورودی اجرا می‌شود. این متد در Cmd یک stub است؛ وجود دارد تا در زیرکلاس‌ها بازنویسی شود. مقدار بازگشتی به‌عنوان فرمانی استفاده می‌شود که متد onecmd() آن را اجرا خواهد کرد؛ پیاده‌سازی precmd() ممکن است فرمان را بازنویسی کند یا صرفاً line را بدون تغییر بازگرداند.

Cmd.postcmd(stop, line)

متد قلابی که درست پس از پایان توزیع یک فرمان اجرا می‌شود. این متد یک stub در Cmd است و برای بازنویسی توسط زیرکلاس‌ها وجود دارد. line خط فرمانی است که اجرا شده است و stop پرچمی است که نشان می‌دهد آیا اجرا پس از فراخوانی postcmd() خاتمه خواهد یافت یا خیر؛ این، مقدار بازگشتی متد onecmd() خواهد بود. مقدار بازگشتی این متد به‌عنوان مقدار جدید برای پرچم داخلی متناظر با stop استفاده خواهد شد؛ بازگرداندن false باعث ادامه‌ی تفسیر خواهد شد.

Cmd.preloop()

متد قلابکه یک بار هنگام فراخوانی cmdloop() اجرا می‌شود. این متد در Cmd یک متد stub است؛ وجود دارد تا توسط زیرکلاس‌ها بازنویسی شود.

Cmd.postloop()

متد قلابکه یک بار اجرا می‌شود، هنگامی که cmdloop() در آستانه‌ی بازگشت است. این متد در Cmd یک stub است؛ این متد وجود دارد تا توسط زیرکلاس‌ها بازنویسی شود.

نمونه‌های زیرکلاس‌های Cmd دارای چند متغیر نمونه عمومی هستند:

Cmd.prompt

اعلان صادرشده برای درخواست ورودی.

Cmd.identchars

رشته‌ای از نویسه‌ها که برای پیشوند فرمان پذیرفته می‌شود.

Cmd.lastcmd

آخرین پیشوند غیرخالی دستور مشاهده‌شده.

Cmd.cmdqueue

فهرستی از سطرهای ورودی صف‌شده. فهرست cmdqueue در cmdloop() هنگامی که به ورودی جدیدی نیاز باشد بررسی می‌شود؛ اگر خالی نباشد، عناصر آن به ترتیب پردازش می‌شوند، گویی در اعلان وارد شده‌اند.

Cmd.intro

رشته‌ای که به‌عنوان مقدمه یا بنر صادر می‌شود. ممکن است با دادن یک آرگومان به متد cmdloop() بازنویسی شود.

Cmd.doc_header

سرآیندی که اگر خروجی راهنما دارای بخشی برای دستورهای مستندشده باشد، نمایش داده می‌شود.

Cmd.misc_header

سرآیندی که اگر خروجی راهنما دارای بخشی برای موضوعات متفرقه‌ی راهنما باشد، صادر می‌شود (یعنی متدهای help_*() بدون متدهای متناظر do_*() وجود داشته باشند).

Cmd.undoc_header

سرآیند‌ای که در صورت وجود داشتن بخشی برای فرمان‌های مستندنشده در خروجی help، نمایش داده می‌شود (یعنی متدهای do_*() بدون متدهای help_*() متناظر وجود دارند).

Cmd.ruler

نویسه‌ای که برای رسم سطرهای جداکننده زیر سرآیندهای پیام راهنما استفاده می‌شود. اگر خالی باشد، هیچ خط جداکننده‌ای رسم نمی‌شود. مقدار پیش‌فرض آن '=' است.

Cmd.use_rawinput

پرچمی که مقدار پیش‌فرض آن true است. اگر true باشد، cmdloop() از input() برای نمایش یک اعلان و خواندن فرمان بعدی استفاده می‌کند؛ اگر false باشد، از sys.stdout.write() و sys.stdin.readline() استفاده می‌شود. (این بدان معناست که با ایمپورت کردن readline، در سامانه‌هایی که از آن پشتیبانی می‌کنند، مفسر به‌طور خودکار از ویرایش خط مشابه Emacs و کلیدهای تاریخچه‌ی فرمان پشتیبانی می‌کند.)

مثال Cmd

ماژول cmd عمدتاً برای ساخت پوسته‌های سفارشی مفید است که به کاربر امکان می‌دهند با یک برنامه به‌صورت تعاملی کار کند.

این بخش یک مثال ساده از چگونگی ساختن پوسته‌ای حول چند مورد از دستورات ماژول turtle ارائه می‌دهد.

دستورات پایه turtle مانند forward() به یک زیرکلاس از Cmd با متدی به نام do_forward() اضافه می‌شوند. آرگومان به یک عدد تبدیل می‌شود و به ماژول turtle فرستاده می‌شود. رشته مستند در ابزار راهنمای ارائه‌شده توسط پوسته استفاده می‌شود.

این مثال همچنین شامل یک امکان ضبط و بازپخش ساده است که با متد precmd() پیاده‌سازی شده است؛ این متد مسئول تبدیل ورودی به حروف کوچک و نوشتن دستورات در یک پرونده است. متد do_playback() پرونده را می‌خواند و دستورات ضبط‌شده را برای بازپخش فوری به cmdqueue اضافه می‌کند:

import cmd, sys
from turtle import *

class TurtleShell(cmd.Cmd):
    intro = 'Welcome to the turtle shell.   Type help or ? to list commands.\n'
    prompt = '(turtle) '
    file = None

    # ----- basic turtle commands -----
    def do_forward(self, arg):
        'Move the turtle forward by the specified distance:  FORWARD 10'
        forward(*parse(arg))
    def do_right(self, arg):
        'Turn turtle right by given number of degrees:  RIGHT 20'
        right(*parse(arg))
    def do_left(self, arg):
        'Turn turtle left by given number of degrees:  LEFT 90'
        left(*parse(arg))
    def do_goto(self, arg):
        'Move turtle to an absolute position with changing orientation.  GOTO 100 200'
        goto(*parse(arg))
    def do_home(self, arg):
        'Return turtle to the home position:  HOME'
        home()
    def do_circle(self, arg):
        'Draw circle with given radius an options extent and steps:  CIRCLE 50'
        circle(*parse(arg))
    def do_position(self, arg):
        'Print the current turtle position:  POSITION'
        print('Current position is %d %d\n' % position())
    def do_heading(self, arg):
        'Print the current turtle heading in degrees:  HEADING'
        print('Current heading is %d\n' % (heading(),))
    def do_color(self, arg):
        'Set the color:  COLOR BLUE'
        color(arg.lower())
    def do_undo(self, arg):
        'Undo (repeatedly) the last turtle action(s):  UNDO'
    def do_reset(self, arg):
        'Clear the screen and return turtle to center:  RESET'
        reset()
    def do_bye(self, arg):
        'Stop recording, close the turtle window, and exit:  BYE'
        print('Thank you for using Turtle')
        self.close()
        bye()
        return True

    # ----- record and playback -----
    def do_record(self, arg):
        'Save future commands to filename:  RECORD rose.cmd'
        self.file = open(arg, 'w')
    def do_playback(self, arg):
        'Playback commands from a file:  PLAYBACK rose.cmd'
        self.close()
        with open(arg) as f:
            self.cmdqueue.extend(f.read().splitlines())
    def precmd(self, line):
        line = line.lower()
        if self.file and 'playback' not in line:
            print(line, file=self.file)
        return line
    def close(self):
        if self.file:
            self.file.close()
            self.file = None

def parse(arg):
    'Convert a series of zero or more numbers to an argument tuple'
    return tuple(map(int, arg.split()))

if __name__ == '__main__':
    TurtleShell().cmdloop()

در اینجا یک نشست نمونه با پوسته turtle آمده است که توابع راهنما، استفاده از سطرهای خالی برای تکرار دستورها و قابلیت ساده ضبط و پخش را نشان می‌دهد:

به پوسته turtle خوش آمدید.   برای فهرست کردن دستورها، help یا ? را وارد کنید.

(turtle) ?

دستورهای مستندشده (help <topic> را وارد کنید):
========================================
bye     color    goto     home  playback  record  right
circle  forward  heading  left  position  reset   undo

(turtle) help forward
لاک‌پشت را به اندازه فاصله مشخص‌شده به جلو حرکت دهید:  FORWARD 10
(turtle) record spiral.cmd
(turtle) position
موقعیت فعلی ۰ ۰ است

(turtle) heading
جهت فعلی ۰ است

(turtle) reset
(turtle) circle 20
(turtle) right 30
(turtle) circle 40
(turtle) right 30
(turtle) circle 60
(turtle) right 30
(turtle) circle 80
(turtle) right 30
(turtle) circle 100
(turtle) right 30
(turtle) circle 120
(turtle) right 30
(turtle) circle 120
(turtle) heading
جهت فعلی ۱۸۰ است

(turtle) forward 100
(turtle)
(turtle) right 90
(turtle) forward 100
(turtle)
(turtle) right 90
(turtle) forward 400
(turtle) right 90
(turtle) forward 500
(turtle) right 90
(turtle) forward 400
(turtle) right 90
(turtle) forward 300
(turtle) playback spiral.cmd
موقعیت فعلی ۰ ۰ است

جهت فعلی ۰ است

جهت فعلی ۱۸۰ است

(turtle) bye
از شما برای استفاده از Turtle سپاسگزاریم