اذهب إلى المحتوى

المسارات paths وشجرة الوحدة module tree في رست Rust


Naser Dakhel

يُستخدم المسار path بنفس الطريقة المُستخدمة عند التنقل ضمن نظام الملفات في الحاسوب حتى ندلّ رست على مكان وجود عنصر ما ضمن شجرة الوحدة module tree، وبالتالي علينا معرفة مسار الدالة أولًا إذا أردنا استدعائها.

قد يكون المسار واحدًا من النوعين التاليين:

  • المسار المُطلق absolute path: وهو المسار الكامل بدءًا من جذر الوحدة المصرّفة؛ إذ أن المسار المُطلق لشيفرة برمجية موجودة في وحدة مصرّفة خارجية تبدأ باسم الوحدة المصرّفة بينما يبدأ مسار شيفرة برمجية داخل الوحدة المصرّفة الحالية المُستخدمة بالكلمة crate.
  • المسار النسبي relative path: ويبدأ هذا المسار من الوحدة المصرّفة الحالية المُستخدمة ويستخدم الكلمة المفتاحية self، أو super، أو معرّف ينتمي إلى الوحدة نفسها.

يُتبع كلا نوعَي المسارات السابقَين بمعرّف identifier واحد، أو أكثر ويفصل بينهما نقطتان مزدوجتان ::.

بالعودة إلى الشيفرة 1 في المقالة السابقة، لنفترض أننا نريد استدعاء الدالة add_to_waitlist، وهذا يقتضي أن نسأل أنفسنا: ما هو مسار الدالة add_to_waitlist؟ تحتوي الشيفرة 3 على الشيفرة 1 مع إزالة بعض الوحدات والدوال.

سنستعرض طريقتين لاستدعاء الدالة add_to_waitlist من دالة جديدة معرّفة في جذر الوحدة المصرّفة وهي eat_at_restaurant في هذه الحالة، والمسارات الموجودة في كل من الطريقتين صالحة إلا أن هناك مشكلة أخرى ستمنع مثالنا من أن يُصرَّف كما هو، وسنفسّر ذلك قريبًا.

تشكل الدالة eat_at_restaurant جزءًا من الواجهة البرمجية العامة public API الخاصة بوحدة المكتبة المصرّفة library crate، لذا نُضيف الكلمة المفتاحية pub إليها، وسنناقش المزيد من التفاصيل بخصوص pub في الفقرة التالية.

اسم الملف: src/lib.rs

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // مسار مطلق
    crate::front_of_house::hosting::add_to_waitlist();

    // مسار نسبي
    front_of_house::hosting::add_to_waitlist();
}

[الشيفرة 3: استدعاء الدالة addtowaitlist باستخدام المسار المُطلق والمسار النسبي]

نستخدم مسارًا مُطلقًا عندما نريد استدعاء الدالة add_to_waitlist داخل eat_at_restaurant للمرة الأولى، ويمكننا استخدام المسار المطلق بدءًا بالكلمة المفتاحية crate بالنظر إلى أن الدالة add_to_waitlist معرفة في نفس الوحدة المصرّفة الخاصة بالدالة eat_at_restaurant، ومن ثمّ نضمّن كل من الوحدات الأخرى الموجودة في شجرة الوحدة module tree حتى الوصول إلى الدالة add_to_waitlist. يمكنك تخيّل هذا الأمر بصورةٍ مشابهة لنظام الملفات على حاسوبك، إذ نكتب المسار ‎/front_of_house/hosting/add_to_waitlist لتشغيل البرنامج add_to_waitlist، إلا أننا نستخدم الكلمة المفتاحية crate للدلالة على جذر الوحدة المصرّفة بدلًا من استخدام / في بداية المسار، وهو أمرٌ مشابه لكتابة / في سطر الأوامر حتى تصل إلى جذر نظام الملفات على حاسوبك.

نستخدم المسار النسبي في المرة الثانية التي نستدعي add_to_waitlist في eat_at_restaurant، ويبدأ المسار هنا بالاسم front_of_house، وهو اسم الوحدة المعرفة ضمن نفس مستوى eat_at_restaurant في شجرة الوحدة، وسيستخدم نظام الملفات المكافئ المسار front_of_house/hosting/add_to_waitlist بدءًا باسم الوحدة مما يعني أن المسار هو مسار نسبي.

يجب الاختيار بين المسار المطلق والنسبي بناءً على مشروعك ويعتمد على إذا ما كنت ستميل غالبًا إلى نقل شيفرة تعريف العنصر البرمجية وتفريقها عن الشيفرة البرمجية التي تستخدم ذلك العنصر، أو إذا كنت ستبقيهما سويًا. على سبيل المثال، إذا أردنا نقل الوحدة front_of_house والدالة eat_at_restaurant إلى وحدة باسم customer_experience، سنضطر إلى تحديث المسار المطلق الخاص بالدالة add_to_waitlist، إلا أن المسار سيبقى صالحًا في حال استخدمنا المسار النسبي، لكن إذا نقلنا الدالة eat_at_restaurant بصورةٍ منفصلة إلى وحدة جديدة تدعى dining فلن يكون المسار النسبي لاستدعاء الدالة add_to_waitlist صالحًا بعد الآن ويجب تحديثه، إلا أن المسار المطلق سيبقى صالحًا. نفضّل هنا المسارات المطلقة، لأننا على الأغلب سنحرّك تعريف الشيفرة البرمجية واستدعاء العناصر على نحوٍ متفرق عن بعضهما بعضًا.

دعنا نجرّب تصريف الشيفرة 3 ونقرأ الخطأ ونحاول معرفة السبب في عدم قابلية تصريفها. نحصل على الخطأ الموضح في الشيفرة 4.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
 --> src/lib.rs:9:28
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                            ^^^^^^^ private module
  |
note: the module `hosting` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hosting {
  |     ^^^^^^^^^^^

error[E0603]: module `hosting` is private
  --> src/lib.rs:12:21
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                     ^^^^^^^ private module
   |
note: the module `hosting` is defined here
  --> src/lib.rs:2:5
   |
2  |     mod hosting {
   |     ^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` due to 2 previous errors

[الشيفرة 4: أخطاء المصرف الناجمة عن تصريف الشيفرة 3]

تدلنا رسالة الخطأ على أن الوحدة hosting هي وحدة خاصة، بمعنى أنه على الرغم من استخدامنا للمسار الصحيح الخاص بوحدة hosting ودالة add_to_waitlist إلا أن رست لن تسمح لنا باستخدامهما لأنه لا يوجد لدينا سماحية الوصول إلى هذه الأجزاء الخاصة. جميع العناصر في رست (دوال وتوابع وهياكل وتعدادات ووحدات وثوابت) هي خاصة بالوحدة الأب (الأصل) فقط افتراضيًا، وإذا أردت جعل عنصر ما مثل دالة أو هيكل خاصًا، فعليك وضعه داخل الوحدة.

لا يمكن لعناصر في وحدة أب استخدام العناصر الخاصة داخل الوحدات التابعة لها، إلا أن وحدات الابن يمكن أن تستخدم العناصر الموجودة في الوحدات الأب، وهذا لأن الوحدات الابن تغلّف وتُخفي تفاصيل تطبيقها وتستطيع الوحدات الابن رؤية السياق الخاص بتعريفها، وحتى نستمرّ في تشبيهنا السابق للمطعم، تخيل أن قوانين الخصوصية مشابهة للمكاتب الإدارية للمطعم، فالذي يحصل في هذه المكاتب هو معزول عن زبائن المطعم، لكن يستطيع مدراء المطعم رؤية أي شيء في المطعم.

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

كشف المسارات باستخدام الكلمة المفتاحية pub

بالعودة إلى الخطأ الموجود في الشيفرة 4 الذي كان مفاده أن الوحدة hosting هي وحدة خاصة، نريد أن تمتلك الدالة eat_at_restaurant الموجودة في الوحدة الأب وصولًا إلى الدالة add_to_waitlist الموجودة في الوحدة الابن، ولتحقيق ذلك نُضيف الكلمة المفتاحية pub إلى الوحدة hosting كما هو موضح في الشيفرة 5.

اسم الملف: src/lib.rs

mod front_of_house {
    pub mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // مسار مطلق
    crate::front_of_house::hosting::add_to_waitlist();

    // مسار نسبي
    front_of_house::hosting::add_to_waitlist();
}

[الشيفرة 5: التصريح عن الوحدة hosting باستخدام الكلمة المفتاحية pub لاستخدامها داخل eatatrestaurant]

لسوء الحظ، تتسبب الشيفرة 5 بخطأ موضح في الشيفرة 6.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
 --> src/lib.rs:9:37
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                                     ^^^^^^^^^^^^^^^ private function
  |
note: the function `add_to_waitlist` is defined here
 --> src/lib.rs:3:9
  |
3 |         fn add_to_waitlist() {}
  |         ^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `add_to_waitlist` is private
  --> src/lib.rs:12:30
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                              ^^^^^^^^^^^^^^^ private function
   |
note: the function `add_to_waitlist` is defined here
  --> src/lib.rs:3:9
   |
3  |         fn add_to_waitlist() {}
   |         ^^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` due to 2 previous errors

[شيفرة 6: أخطاء المصرّف الناتجة عن بناء الشيفرة 5]

ما الذي حصل؟ تجعل إضافة الكلمة المفتاحية pub أمام mod hosting من الوحدة وحدةً عامةً، وبهذا التغيير إن أمكننا الوصول إلى front_of_house، فهذا يعني أنه يمكننا الوصول إلى hosting، ولكن محتوى hosting ما زال خاصًّا، إذ أن تحويل الوحدة إلى وحدة عامة لا يجعل من محتواها عامًا أيضًا، إذ أن استخدام الكلمة المفتاحية pub على وحدة ما يسمح الوحدات الأب بالإشارة إلى الشيفرة البرمجية فقط وليس الوصول إلى الشيفرة البرمجية الداخلية، ولأن الوحدات هي بمثابة حاويات فليس هناك الكثير لفعله بجعل الوحدة فقط عامة، وسنحتاج إلى جعل عنصر واحد أو أكثر من عناصرها عامًا أيضًا.

تدلنا الأخطاء الموجودة في الشيفرة 6 إلى أن الدالة add_to_waitlist هي دالة خاصة، وتُطبَّق قوانين الخصوصية على الهياكل والتعدادات والتوابع والدوال كما هو الحال مع الوحدات.

دعنا نجعل من الدالة add_to_waitlist دالة عامة بإضافة الكلمة المفتاحية pub قبل تعريفها كما هو موضح في الشيفرة 7.

اسم الملف: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // مسار مطلق
    crate::front_of_house::hosting::add_to_waitlist();

    // مسار نسبي
    front_of_house::hosting::add_to_waitlist();
}

[الشيفرة 7: إضافة الكلمة المفتاحية pub إلى mod hosting و fn addtowaitlist مما يسمح لنا باستدعاء الدالة من eatatrestaurant]

يمكننا الآن تصريف الشيفرة البرمجية بنجاح. دعنا ننظر إلى المسارات المطلقة والمسارات النسبية حتى نفهم لماذا تسمح لنا إضافة pub باستخدام هذه المسارات في add_to_waitlist مع مراعاة قوانين الخصوصية.

نبدأ بكتابة crate في المسار المطلق وهو جذر شجرة الوحدة الخاصة بالوحدة المصرّفة. تُعرّف الوحدة front_of_house في جذر الوحدة المصرّفة، إلا أنها ليست عامة، وذلك لأن الدالة eat_at_restaurant معرفة في الوحدة ذاتها الخاصة بالوحدة front_of_house (أي أن eat_at_restaurant و front_of_house أشقاء)، ويُمكننا الإشارة إلى front_of_house من eat_at_restaurant. تاليًا تبدو الوحدة hosting معلّمة marked بفضل الكلمة المفتاحية pub، إذ يمكننا الوصول إلى الوحدة الأب الخاصة بالوحدة hosting، وبالتالي يمكننا الوصول إلى hosting، وأخيرًا، الدالة add_to_waitlist مُعلّمة بالكلمة المفتاحية pub، وبالتالي يعمل بقية المسار ويُعد استدعاء الدالة هذا صالحًا.

نعتمد في المسار النسبي نفس المنطق في المسار المطلق باستثناء الخطوة الأولى، إذ بدلًا من البدء بجذر الوحدة المصرّفة، يبدأ المسار من الوحدة front_of_house، التي تُعرّف في الوحدة ذاتها الخاصة بالوحدة eat_at_restaurant، وبالتالي يبدأ المسار النسبي من الوحدة التي يعمل عندها تعريف الوحدة eat_at_restaurant. تبدو الوحدة hosting و add_to_waitlist معلّمة بفضل الكلمة المفتاحية pub، ولذلك يعمل بقية المسار ويُعد استدعاء الدالة هذا صالحًا.

إذا أردت مشاركة وحدة المكتبة المصرّفة الخاصة بك بحيث تستفيد مشاريع أخرى من الشيفرة البرمجية الخاصة بالوحدة المصرّفة، ستكون واجهتك البرمجية API العامة هي نقطة الوصل بين مستخدمي الوحدة المصرّفة ومحتوياتها وهي التي تحدد كيفية تفاعل المستخدمين مع شيفرتك البرمجية، وهناك الكثير من الأشياء التي يجب وضعها في الحسبان لإدارة التغييرات التي تجريها على الواجهة البرمجية العامة حتى تجعل عملية الاعتماد على وحدتك المصرّفة أسهل بالنسبة للمستخدمين، إلا أن هذه الأشياء خارج نطاق موضوعنا هنا، وإذا كنت مهتمًا بهذا الخصوص عليك رؤية دليل رست الخاصة بالواجهات البرمجية.

الممارسات المثلى للحزم التي تحتوي على وحدات ثنائية مصرفة ووحدات مكتبة مصرفة

ذكرنا أنه يمكن أن تحتوي الحزمة على كلٍّ من جذر وحدة ثنائية مصرّفة src/main.rs إضافةً إلى جذر وحدة مكتبة مصرّفة src/lib.rs وأن يحمل كلتا الوحدتين المصرّفتين اسم الحزمة افتراضيًا، ويحتوي هذا النوع من الحزم عادةً على شيفرة برمجية كافية داخل الوحدة الثنائية المصرّفة، بحيث يمكن إنشاء ملف تنفيذي باستخدامها يستدعي الشيفرة البرمجية الموجودة في وحدة المكتبة المصرّفة، مما يسمح للمشاريع الأخرى بالاستفادة القصوى من المزايا التي تقدمها هذه الحزمة وذلك لأنه من الممكن مشاركة الشيفرة البرمجية الموجودة داخل وحدة المكتبة المصرّفة.

يجب أن تُعرَّف شجرة الوحدة في الملف src/lib.rs، ومن ثمّ يمكننا استخدام أي عنصر عام في الوحدة الثنائية المُصرّفة ببدء المسار باسم الحزمة، وبذلك تصبح الوحدة الثنائية المصرّفة مستخدمًا user لوحدة المكتبة المصرّفة كما تستخدم أية وحدة مصرّفة خارجية وحدة مكتبة مصرّفة عادةً، وذلك باستخدام الواجهة البرمجية العامة فقط. يساعدك ذلك في تصميم واجهة برمجية جيّدة؛ إذ أنك لست كاتب المكتبة فقط في هذه الحالة، بل أنك مستخدمها أيضًا.

سنستعرض الممارسات الشائعة في تنظيم برامج سطر الأوامر التي تحتوي على كل من الوحدة الثنائية المصرّفة ووحدة المكتبة المصرّفة لاحقًا.

كتابة المسارات النسبية باستخدام super

يُمكننا إنشاء مسار نسبي يبدأ في الوحدة الأب بدلًا من الوحدة الحالية أو جذر وحدة مصرّفة باستخدام الكلمة المفتاحية super في بداية المسار، وهذا يُشابه بدء مسار الملفات بالنقطتين ..، إذ يسمح لنا استخدام super بالإشارة إلى عنصر نعلم أنه موجود في الوحدة الأب، مما يجعل إعادة ترتيب شجرة الوحدة أسهل عندما تكون الوحدة مرتبطة بصورةٍ وثيقة مع الوحدة الأب مع احتمال نقل الوحدة الأب إلى مكان آخر ضمن شجرة الوحدة في يوم ما.

ألقِ نظرةً إلى الشيفرة 8 التي تصف موقفًا معينًا، ألا وهو تصحيح الطاهي لطلب غير صحيح وإحضاره بنفسه شخصيًا إلى الزبون. تستدعي الدالة fix_incorrect_order المُعرفة في الوحدة back_of_house الدالة deliver_order المعرّفة في الوحدة الأب بتحديد المسار إلى الدالة deliver_order باستخدام الكلمة المفتاحية super في البداية:

اسم الملف: src/lib.rs

fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}

[الشيفرة 8: استدعاء دالة باستخدام المسار النسبي بدءًا بالكلمة super]

تتواجد الدالة fix_incorrect_order في الوحدة back_of_house، لذا يمكننا استخدام super للإشارة إلى الوحدة الأب الخاصة بالوحدة back_of_house التي هي في هذه الحالة الوحدة crate الجذر، ومن هناك نبحث عن deliver_order ونجده بنجاح. نعتقد هنا أن الوحدة back_of_house والدالة deliver_order سيبقيان سويًا نظرًا للعلاقة فيما بينهما مهما تغيّر تنظيم شجرة الوحدة، وبالتالي استخدمنا super بحيث نختصر على أنفسنا أماكن تحديث الشيفرة البرمجية في المستقبل إذا قرّرنا نقل الشيفرة البرمجية إلى وحدة مختلفة.

جعل الهياكل والتعدادات عامة

يمكننا أيضًا استخدام الكلمة المفتاحية pub لتعيين الهياكل structs والتعدادات enums على أنها عامة، إلا أن هناك بعض التفاصيل الإضافية لاستخدام pub مع الهياكل والتعدادات؛ فإذا استخدمنا pub قبل تعريف الهيكل، فسيتسبب هذا بإنشاء هيكل عام إلا أن حقول هذا الهيكل ستظلّ خاصة، ويمكن جعل كل حقل عامًا أو لا اعتمادًا على أسس وشروط كل حالة بحد ذاتها. نُعرّف في الشيفرة 9 هيكلًا عامًا باسم back_of_house::Breakfast بحقل عام toast وحقل خاص seasonal_fruit، يُحاكي هذا الأمر عملية اختيار الزبون لنوع الخبز الذي يأتي مع الوجبة واختيار الطاهي لنوع الفاكهة الذي يأتي مصحوبًا مع الوجبة بناءً على المتاح في مخزون المطعم؛ ولأن الفاكهة المُتاحة تتغير سريعًا حسب المواسم فالزبون لا يستطيع اختيار الفاكهة أو حتى رؤية النوع الذي سيحصل عليه.

اسم الملف: src/lib.rs

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    //  Rye طلب فطور في الصيف مع خبز محمص من النوع راي
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // غيّرنا رأينا بخصوص الخبز الذي نريده
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // لن يُصرَّف السطر التالي إذا أزلنا تعليقه لأنه من غير المسموح رؤية أو تعدي الفاكهة الموسمية التي تأتي مع الوجبة
    // meal.seasonal_fruit = String::from("blueberries");
}

[الشيفرة 9: هيكل يحتوي على بعض الحقول العامة والخاصة]

بما أن الحقل toast في الهيكل back_of_house::Breakfast هو حقل عام، فهذا يعني أننا نستطيع الكتابة إلى والقراءة من الحقل toast في eat_at_restaurant باستخدام النقطة. لاحظ أنه لا يمكننا استخدام الحقل seasonal_fruit في eat_at_restaurant وذلك لأن الحقل seasonal_fruit هو حقل خاص. حاول إزالة تعليق السطر البرمجي الذي يُعدّل من قيمة الحقل seasonal_fruit وستحصل على خطأ.

لاحظ أن الهيكل back_of_house::Breakfast بحاجة لتوفير دالة عامة مرتبطة به تُنشئ نسخةً من Breakast (سميناها summer في هذا المثال)، لأنه يحتوي على حقل خاص، ولن يصبح بالإمكان إنشاء نسخة من الهيكل Breakfast في eat_at_restaurant إذا لم يحتوي على دالة مشابهة، وذلك لأنه لن يكون بإمكاننا ضبط قيمة للحقل الخاص seasonal_fruit في eat_at_restaurant.

وفي المقابل، إذا أنشأنا تعدادًا عامًا، ستكون جميع متغايراته variants عامة، ونحن بحاجة لاستخدام الكلمة المفتاحية pub مرةً واحدةً فقط قبل الكلمة enum كما هو موضح في الشيفرة 10.

اسم الملف: src/lib.rs

mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

[الشيفرة 10: تحديد المعدد على أنه عام يجعل جميع متغايراته عامة]

يمكننا استخدام المتغايرات Soup و Salad في eat_at_restaurant وذلك لأننا أنشأنا التعداد Appetier على أنه تعداد عام.

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

هناك بعض الحالات الأخرى بالتي تتضمن pub التي لم نناقشها بعد، ألا وهي آخر مزايا نظام الوحدات: الكلمة المفتاحية use، والتي سنغطّيها تاليًا، ومن ثمّ سنناقش كيفية دمج pub و use معًا.

ترجمة -وبتصرف- لقسم من الفصل Managing Growing Projects with Packages, Crates, and Modules من كتاب The Rust Programming Language.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...