أكتب برامج CLI بسعادة مع مكتبة Click في لغة البايثون - تطبيق عملي

أكتب برامج CLI بسعادة مع مكتبة Click في لغة البايثون – تطبيق عملي

أهلا بكم في بايثونات في هذا المقال الجديد والمميز. من أجمل و أمتع مميزات لغة البايثون هي كتابة برامج سطر الأوامر CLI – Command Line Interface كما هو الحال المعتاد بشكل قياسي في برامج لينكس و يونكس، حيث نكتب إسم البرنامج داخل نافذة الأوامر terminal في ماك و لينكس أو cmd في ويندوز، ملحوقًا بمعاملات أو أي بيانات تريد تغذيتها للبرنامج الرئيسي ليتم معالجتها أو استخدامها للحصول على نتيجة ما ترغب بها مباشرة.

تأتي لغة البايثون مع مكتبات ممتازة لهذا الغرض مثل aptparse و argparse. تعد مكتبة argparse هي الأكثر تطورًا، حيث تأتي بخيارات و دوال عديدة تُضيف مميزات تفاعلية جميلة لبرامج CLI. على الرغم من مميزاتها، إلا أنها تتطلب كتابة شيفرات طويلة نسبيًا و محاولة فهم طريقة تركيبها بشكل خاص لنتمكن من استيعابها بكل سلاسة.

في هذا المقال نستعرض معكم كيفية استخدام مكتبة أوامر جديدة وسهلة في لغة البايثون و سلسة وقوية بنفس الوقت تُسمى click.

يمكنك أن تتصفح الموقع الرسمي للمكتبة هنا:

https://click.palletsprojects.com/en/7.x/

يمكن تثبيت مكتبة click من خلال مثبت الحزم pip في لغة البايثون بالشكل التالي:

pip3 install click

مفاهيم هامة في مكتبة click

من المهم في البداية أن نتفق على مفاهيم ومفردات معينة لنستخدمها في هذا الدرس ليتم استيعابه بشكل جيد و لنتمكن من فهم الشيفرات البرمجية المكتوبة وبناء CLI و التفاعل مع الشيفرات البرمجية في لغة البايثون بالشكل الذي نريد.

المعاملات Arguments: هي قيم لابد من كتابتها بشكل إجباري وبالترتيب بسطر الأوامر بعد كتابة إسم البرنامج، وهي قيم إجبارية يحتاجها برنامجك ليعمل بشكل صحيح (كما تصممه و كما سنرى و نتعلم ذلك بشكل عملي).

الإختيارات Options: هي قيم اختيارية، أي أن المستخدم للبرنامج الذي قمت بتصميمه مخير أن يدخل تلك القيم أو يتركها. تلك القيم ليس مهما أن تدخل بالترتيب، بل يتم إدخالها بعد كتابة رمز يبين أن تلك القيمة يجب أن تخزن بداخله، أو حتى لتقوم تلك الرموز الاختيارية بتفعيل دوال معينة إضافية كإجراءات إضافية للنتائج التي نريد الحصول عليها.

الجزء العملي في مكتبة click

لنقم الآن بكتابة برنامج بسيط باستخدام لغة البايثون يقوم بطباعة الإسم الأول واسم العائلة للمستخدم باستخدام مكتبة click. سنجعل من الإسم الأول معامل إجباري واسم العائلة اختياري، وستكون طريقة استدعاء البرنامج كالتالي:

python3 click_play.py nasser --family alostath

البرنامج

#!/usr/bin/env python3

# importing click
import click , os

# click uses decorators ( starting with @ ) to add commands,arguments,options ...etc
@click.command() # main command within which all arguments and options go
@click.option('--family','-f') # an option ,with long and short form
@click.argument('name') # an argument

# main function , where the variables created above as an option and argument are passed
def main(family,name):
    os.system('clear') # to clear the screen on launch

    # if the family name is None just make it equal to nothing
    if family == None:
        family =""
    # print out the name / full name
    print(f'hello {name} {family}...')

if __name__ == '__main__':
    main()

والآن لنشرح البرنامج أعلاه:

import click , os

استوردنا مكتبة click ومكتبة os لنستخدم الدالة system في تمرير وتنفيذ أوامر لنافذة الأوامر، بحيث سنستخدمها لتنظيف نافذة الأوامر في كل مرة نُنفذ فيها البرنامج كما سنرى لاحقا.

@click.command() # main command within which all arguments and options go
@click.option('--family','-f') # an option ,with long and short form
@click.argument('name') # an argument

في الأسطر أعلاه نلاحظ  أن click تعتمد بشكل أساسي على المزخرفات decorators والتي تبدأ بالرمز @. لن نتطرق لشرح طريقة عمل المزخرفات هنا ولكن يكفينا أن نأخذ فكرة عامة عنها. المزخرفات هي دوال تأخذ دوال كمعاملات لها وتقوم بإرجاع دوال مُحسنة او مُعدلة من المدخلة كقيم مرجعة منها. أما هنا في هذا الدرس فيكفينا أن نفهم أنها طريقة لإنشاء المعاملات و الإختيارات في click.

@click.command()

يُنشئ أمر رئيسي تندرج تحته جميع المعاملات والإختيارات التي سننشئها لاحقا.

@click.option('--family','-f')

يُنشئ اختيار option، بحيث أن family– هي الصيغة المطولة منه و f- هي الصيغة المصغرة. المستخدم مُخير أن يستخدم أي من الصيغتين أثناء تشغيل البرنامج من سطر الأوامر. ما يليه هي القيمة التي نريد أن يأخذها الإختيار.

@click.argument('name')

يُنشئ معامل إجباري Argument أي لابد للمستخدم أن يدخل قيمة له أثناء التشغيل بسطر الأوامر ليعمل البرنامج. ذلك يعني أن المعاملات الإجبارية عندما نعينها في برنامجنا هي شرط أساسي للبرنامج ليشتغل.

نلاحظ أننا مررنا المتغيرات name و family للدالة main. تلك المتغيرات هي نفسها التي قمنا بإنشائها كمعامل وإختيار من الأسطر السابقة ليتم الاستفادة منها لاحقا.

os.system('clear') # to clear the screen on launch

فهو لتنظيف نافذة الأوامر بحيث أن كلمة clear هي أمر من أوامر نافذة الأوامر في لينكس و ماك و تقوم os.system نفسها بتنفيذها في النافذة. أما في ويندوز فيمكنك إستبدال clear بـ cls ليتم نفس العمل.

وأخيرا العبارة الشرطية هي لتختبر إذا ما كان اسم العائلة family يساوي القيمة None أي لا شيء لتجعله يساوي محرف طولة صفر “” لأنه في حال لم يتم ذلك سيتم طباعة كلمة None كجزء من المخرجات في دالة الطباعة التي تليها.

  # if the family name is None just make it equal to nothing
    if family == None:
        family =""
    # print out the name / full name
    print(f'hello {name} {family}...')

و الآن جرب البرنامج أولا بكتابة الأمر بالأشكال التالية و لاحظ مخرجاتك:

python3 click_play.py Nasser -f alostath
python3 click_play.py Nasser --family alostath
python3 click_play.py Nasser

وأيضا قم بتجربة التالي ولاحظ التنيجة:

python3 click_play.py --help

نجد أن click قامت و بشكل تلقائي بعمل قائمة المساعدة والتي تشرح البرنامج الذي أعددناه. لاحظ أن المعامل name كُتب بأحرف كبيرة بالأعلى و كأنها جزء من أمر تشغيل البرنامج نفسه، في حين أننا نرى شرح الإختيارات في فقرة Options و فيها إختياراتنا التي أنشأناها، بالإضافة لاختيار help– و التي أعطتنا إياه click كجزء من البرنامج.

مثال آخر

لنقم بكتابة برنامج CLI بسيط بحيث يستقبل الإسم الأول والثاني والعائلة  كمعاملات إجبارية ويطبعها على الشاشة بشكل مرتب مع إضافة إختيار طباعة الأسماء بألوان يمكن إختيارها من قبل المستخدم وكذلك إختيار طباعة العمر. من خلال هذا المثال سنتعلم أشياء أخرى إضافة لما سبق وكذلك سيكون الحال مع باقي الأمثلة في هذا الدرس إن شاء الله.

شكل الـ CLI سيكون مثل الشكل التالي:

python3 showonscreen.py nasser bader alostath --color red --age 38

شرح سطر البرنامج

import os , click


@click.command()
@click.argument('first')
@click.argument('second')
@click.argument('last')
@click.option('--color','-c',help='color',default="white")
@click.option('--age','-a' ,help='age')

def main(first,second,last,color,age):
   # echo can be styles with colors by using style method ,fg        is foreground ,, try bg
   click.echo(
       click.style(f"{first} {second} {last}",fg=color),
      
   )

   click.echo(
       click.style(f"age = {age}",fg='blue',bg='white')
   )

if __name__ == '__main__':
   os.system('clear')
   main()

نلاحظ قوة الوظيفة echo بأنها تقبل الألوان من خلال تمرير دالة أخرى فيها وهي style والتي تمكننا من إضافة لون على النص، حيث أن المعامل fg هو لون الخط وbg هو لون الخلفية التي يكتب عليها الخط.

عند تشغيل البرنامج كما في الشكل السابق تكون النتيجة:

رائع، والآن لنقم بكتابة البرنامج التالي، ثم نشغله ويليه شرح ما هو جديد فيه:
import os
import click

@click.command()
# basic options
@click.option('--name','-n', default="JOHN",help="First name",prompt=True)

#multiple Values
@click.option('--hobbies','-h',help="hobbies",nargs=2)
@click.option('--salary','-s',help="Monthly Salary" ,type=int,nargs=2)

# multiple OPTIONS
# use -l for each extra value you want to enter
@click.option('--locations','-l',help="locations  trained at",multiple=True)

def main(name,hobbies,salary,locations):
  
   click.echo(f"Hello {name} ! ! !\nHobbies : {hobbies}\nSalary : {salary}")
   click.echo(f"locations Trained at : {', '.join(locations)}")
  
if __name__ == "__main__":
   os.system("clear")
   main()

  من النص السابق نجد أنه لدينا عدد من options و التي تحتوي على معاملات جديدة، فمثلا السطر:

@click.option('--name','-n', default="JOHN",help="First 
name",prompt=True)

  نجد أن هناك قيمة قياسية للاختيار name  والتي تم تحديدها بـقيمة المعامل  default وهي JOHN . ولكن ما فائدة المعامل prompt = True ؟ ميزتها بأنها تقوم بطباعة رسالة بإسم الإختيار الذي لم يتم تلقيمه ما إذا يريد المستخدم أن يعطيه قيمة أم لا في حال نسي ذلك . لا تقلق، سنقوم بتجربة جميع الإختيارات عمليا بعد هذه الشرح. جرب أن تشغل البرنامج بالشكل التالي :

python3 p1.py

ثم اضغط enter.

 ستجد أن البرنامج سيقوم بعرض الاختيار name وكأنه يسأل المستخدم اذا يريد إعطاءه قيمة أو يعتمد القيمة الابتدائية المخزنة فيه سلفا. قم بالضغط على زر enter.

Name [JOHN]: 
Hello JOHN ! ! !
Hobbies : ()
Salary : ()
locations Trained at : 

أعد تشغيل البرنامج وأعطي الاختيار name قيمة من عندك.

ملاحظه: يمكنك أن تعطي الاختيار name قيمة أثناء التلقيم بسطر الأوامر مباشرة كما يلي:

python3 p1.py --name nasser

أو:

python3 p1.py -n nasser

 بحيث أن name– و n- تعملان نفس الوظيفة تماما حيث أن الأولى ما هي إلا صيغة مطولة عن الأخرى.

أما السطر:

@click.option('--salary','-s',help="Monthly Salary" ,type=int,nargs=2)

نجد أن هناك معامل nargs=2 ومعناه أن هذا الـ option يحتاج تحديدا لقيمتين ليعمل و يقوم بتخزينها على شكل tuple وكذلك السطر الذي يليه.  هنا نستطيع القول أننا استطعنا تخزين قيمتين في  option واحد.

لنقم باختبار الـ options بتشغيل البرنامج كالتالي:

python3 p1.py --name nasser -h coding training --salary 3000 4000

ستكون المخرجات بالشكل التالي:

Hello nasser ! ! !
Hobbies : ('coding', 'training')
Salary : (3000, 4000)
locations Trained at : 

تلميح: إذا أردنا تخزين عدد غير محدد من القيم فما علينا إلا أن نحدد قيمة nargs  لتساوي  -١ , nargs = -1

و السطر:

@click.option('--locations','-l',help="locations  trained at",multiple=True)

نجد المعامل multiple =True وهو الذي يتيح للمستخدم أن يستخدم الاختيار الواحد أكثر من مرة و تغذيته بقيم مختلفة  كما يلي:

 python3 p1.py --name nasser -h coding training --salary 3000 4000 -l UK -l dubai -l Kuwait -l Montreal

لاحظ أنه استخدمنا الاختيار l- أو location– أكثر من مرة مع أكثر من قيمة في سطر الأوامر. المخرجات:

Hello nasser ! ! !
Hobbies : ('coding', 'training')
Salary : (3000, 4000)
locations Trained at : UK, dubai, Kuwait, Montreal

و الان، لنقم بكتابة برنامج بسيط يستخدم Arguments بدلا من options بحيث يقوم المستخدم بإدخال اسمه ثم عددين و اسم عملية حسابية add, sub , mul, div.

import click , os


@click.command()

@click.argument("name",default="Dane Black")
@click.argument("number1",type=float)
@click.argument("number2",type=float)
@click.argument("method")
def main(name,number1,number2,method):
  
   click.echo(f"hello , {name}")

   if method == "add":
       click.echo(f"{number1} + {number2} = {number1+number2} ")
   elif method =="sub":
       click.echo(f"{number1} - {number2} = {number1-number2} ")
   elif method == "mul":
       click.echo(f"{number1} X {number2} = {number1*number2} ")
   elif method == "div":
       click.echo(f"{number1} / {number2} = {number1/number2} ")
   else:
       click.echo(
           click.style("Undefined method entered",fg="black",bg="red")
       )

if __name__ == '__main__':
   os.system("clear")
   main()

المدخلات و المخرجات:

python3 arguments.py nasser 11 22 add

hello , nasser
11.0 + 22.0 = 33.0


python3 arguments.py nasser 11 22 sub

hello , nasser
11.0 - 22.0 = -11.0 

python3 arguments.py nasser 11 22 mul

hello , nasser
11.0 X 22.0 = 242.0 

python3 arguments.py nasser 11 22 div

hello , nasser
11.0 / 22.0 = 0.5 

python3 arguments.py nasser 11 22 abs
hello , nasser
Undefined method entered

ممتاز جدا.

مثال للاستخدام اليومي

الآن بعد أن تعلمنا مهارات عدة في مكتبة click لنقم بكتابة برنامج ممكن أن نستفيد منه يوميًا ولو بشكل بسيط. البرنامج يقوم بنسخ عدد غير محدد من الملفات  يدخلها المستخدم  في مجلد معين.

أولا لنقم ببعض التحضيرات:

  • أنشئ ملفات عدة في نفس مكان تواجد البرنامج file1 file2 file3 (ملاحظة: ممكن للملفات أن تكون من غير امتداد في ماك و لينكس).
  • أنشئ مجلد فارغ بنفس مكان تواجد البرنامج و لنسميه dirz.
  • سنستخدم مكتبة shutil للوصول لوظيفة نسخ الملفات بشكل سهل باستخدام وظيفة copy.
  • لنسمي برنامجنا  argf_copyfiles.py.
import os
import click
# library for file utilities like copying files
import shutil

@click.command()
@click.argument('destination')
# -1 means + ,, multiple values per option
@click.argument('source',nargs=-1)
def main(source, destination):
   for f in source:
       # copying files with the full path using
       # using the os.getcwd() for current path
       shutil.copy(str(os.getcwd()+"/")+f,destination)
       click.echo(f"Copied {f} to {destination}")

if __name__ == '__main__':
   os.system('clear')
   main()

المدخلات و المخرجات:

python3 argf_copyfiles.py  file1 file2 file3 dirz/

Copied file1 to dirz/
Copied file2 to dirz/
Copied file3 to dirz/

تأكد من وجود الملفات المنسوخة في داخل الدليل.

حان الوقت الآن لتعلم استخدام ميزة جديدة في مكتبة click وهي المجموعات groups وهي طريقة جميلة لاستدعاء وظائف برامجنا المختلفة عن طريق click. لنقم بذلك عن طريق كتابة برنامج يقوم بعمل هاش hashing لأي نص يدخله المستخدم ثم يقوم باستدعاء أي خوارزمية للهاش. سنستخدم الألوان للمخرجات.

import os , click, hashlib


@click.group() #main command
def main():
   print("program running ...")

# subCommands

@main.command() # affiliation to the main command
@click.argument('sha1') #argument for sha1 algorithm
def sha_1(sha1):
   # the help is displayed using the multiline comment
   """SHA1 hashing"""
   hash_object = hashlib.sha1(sha1.encode())
   hash_object = hash_object.hexdigest()
   click.echo(click.style(f"{hash_object}",fg='red'))

@main.command() # affiliation to the main command
@click.argument('md5') #argument for md5 algorithm
def md5(md5):
   # the help is displayed using the multiline comment
   """MD5 hashing"""
   hash_object = hashlib.md5(md5.encode())
   hash_object = hash_object.hexdigest()
   click.echo(click.style(f"{hash_object}",fg='green'))

@main.command() # affiliation to the main command
@click.argument('sha256') #argument for sha256 algorithm
# the help is displayed using the multiline comment
def sha_256(sha256):
   """256 hashing"""
   hash_object = hashlib.sha256(sha256.encode())
   hash_object = hash_object.hexdigest()
   click.echo(click.style(f"{hash_object}",fg='yellow'))

if __name__ == '__main__':
   os.system('clear')
   main()

شكل شاشة المساعدة:

Usage: groups_hash.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  md5      MD5 hashing
  sha-1    SHA1 hashing
  sha-256  256 hashing

شكل المدخلات و المخرجات:

شكل المدخلات والمخرجات

نلاحظ التالي من شكل تنفيذ البرنامج :

  • الأمر group هو الأمر الشامل و يكتب عند الدالة الرئيسية main، والتي تندرج تحته الأوامر الأخرى التي كتبت فوق الدوال مع المعطيات الخاصة بها.
  • لابد من ربط التبعية الدوال الفرعية بالرئيسية main بكتابة ()command@.
  • أننا استدعينا الدوال التي كتبناها بإسمها مباشرة من سطر الأوامر.
  • قامت click باستبدال “_” بـ  “-” بأسماء الدوال.
  • المعطيات تقوم بتغذيتها مباشرة تبعا للدوال التابعة لها عند استدعائها.

في المثال السابق استخدمنا مكتبة hashlib  واستخدمنا وظائف تفعيل خوارزميات الهاش md5 ,sha1 , sha256،  وأنشئنا دوال لكل خوارزمية، وجعلنا group للدالة الرئيسية main، ومن ثم ربطنا الدوال الأخريات بـ main عن طريق ()main.command@، و لكل واحدة منها معطى خاص بها تستخدم قيمته لعمل الهاش وعرضه بلون مميز.

وفي الختام نتمنى أن تكون المقالة قد حازت على رضاكم، حيث شرحنا فيها مكتبة click التي تعتبر مكتبة جميله و ثرية جدا و سهلة الاستخدام لبناء برامج CLI  بشكل ممتع و تفاعلي أكثر من مكتبتي optparse  و argparse القياسيتين.

 

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *