نقل أداة سطر الأوامر من Go / Rust إلى D.

قبل بضعة أيام ، في ردت في "البرمجة" ، شارك باولو هنريكي كوتشي تجربته في تطوير أداة سطر أوامر في Rust and Go ( تُرجمت إلى Habré ). الأداة المعنية هي عميل لمشروع Hashtrack الخاص به. يوفر Hashtrack واجهة برمجة تطبيقات GraphQL والتي يمكن للعملاء من خلالها تتبع علامات تصنيف معينة على Twitter والحصول على قائمة بالتغريدات ذات الصلة في الوقت الفعلي. بناءً على تعليق ، قررت كتابة منفذ في D لتوضيح كيف يمكن استخدام D لأغراض مماثلة. سأحاول الحفاظ على نفس البنية التي استخدمها في منشور مدونته.



المصادر على GitHub



Video عند النقر



كيف أتيت إلى د



السبب الرئيسي هو أن المدونة الأصلية قارنت اللغات المكتوبة بشكل ثابت مثل Go و Rust ، وقدمت إشارات محترمة إلى Nim و Crystal ، لكنها لم تذكر D ، والتي تقع أيضًا في هذه الفئة. لذلك أعتقد أنه سيجعل المقارنة مثيرة للاهتمام.



أحب أيضًا D كلغة وقد ذكرتها في العديد من منشورات المدونات الأخرى.



البيئة المحلية



يحتوي الدليل على معلومات شاملة حول كيفية تنزيل وتثبيت المترجم المرجعي DMD. يمكن لمستخدمي Windows الحصول على برنامج التثبيت ، بينما يمكن لمستخدمي macOS استخدام البيرة المنزلية. على Ubuntu ، أضفت للتو المستودع المناسب واتبعت التثبيت العادي. مع هذا ، لا تحصل فقط على DMD ، ولكن أيضًا تحصل على dub ، مدير الحزم.



لقد قمت بتثبيت Rust حتى أتمكن من الحصول على فكرة عن مدى سهولة البدء. لقد فوجئت بمدى سهولة ذلك. كنت بحاجة فقط إلى تشغيل المثبت التفاعلي ، الذي يعتني بالباقي. احتجت إلى إضافة ~ / .cargo / bin إلى المسار. كان عليك فقط إعادة تشغيل وحدة التحكم حتى تدخل التغييرات حيز التنفيذ.



دعم المحررين



لقد كتبت Hashtrack في Vim دون صعوبة كبيرة ، ولكن هذا على الأرجح لأن لدي فكرة عما يحدث في المكتبة القياسية. لطالما كانت الوثائق مفتوحة ، لأنني في بعض الأحيان كنت أستخدم رمزًا لم أستورده من الحزمة الصحيحة ، أو اتصلت بوظيفة ذات وسيطات خاطئة. لاحظ أنه بالنسبة للمكتبة القياسية ، يمكنك ببساطة كتابة "import std؛" ويكون كل شيء تحت تصرفك. بالنسبة إلى مكتبات الطرف الثالث ، فأنت وحدك.



كنت أشعر بالفضول بشأن حالة مجموعة الأدوات ، لذلك بحثت في المكونات الإضافية لـ IDE المفضل لدي ، Intellij IDEA. لقد وجدت هذاوتثبيته. لقد قمت أيضًا بتثبيت DCD و DScanner عن طريق استنساخ المستودعات الخاصة بهما وبناءهما ، ثم تكوين المكون الإضافي IDEA للإشارة إلى المسارات الصحيحة. اتصل بمؤلف هذه المدونة للتوضيح.



واجهت بعض المشكلات في البداية ، ولكن تم إصلاحها بعد تحديث IDE والمكوِّن الإضافي. إحدى المشكلات التي واجهتها هي أنها لم تستطع التعرف على حزمتي الخاصة واستمرت في تمييزها على أنها "ربما غير محددة". اكتشفت لاحقًا أنه من أجل التعرف عليهم ، كان عليّ وضع "module_module_name؛" في أعلى الملف.



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



إذا كنت تستخدم Windows ، فقد سمعت أشياء جيدة عن VisualD .



إدارة الحزم



Dub هو مدير الحزم الفعلي في D. يقوم بتنزيل وتثبيت التبعيات من code.dlang.org . بالنسبة لهذا المشروع ، كنت بحاجة إلى عميل HTTP لأنني لم أرغب في استخدام cURL. انتهى بي الأمر مع اثنين من التبعيات ، الطلبات والاعتماد عليها ، cachetools ، التي ليس لها تبعية خاصة بها. ومع ذلك ، لسبب ما ، اختار اثني عشر تبعيات أخرى:







أعتقد أن Dub يستخدمها داخليًا ، لكنني لست متأكدًا من ذلك.



لقد حمل Rust الكثير من الصناديق ( تقريبًا: 228 ) ، ولكن هذا على الأرجح لأن إصدار Rust يحتوي على ميزات أكثر من ميزاتي. على سبيل المثال ، قام بتنزيل rpassword ، وهي أداة تخفي أحرف كلمة المرور أثناء كتابتها في الجهاز ، على غرار وظيفة getpass في Python.هذا أحد الأشياء العديدة التي لا أملكها في الكود. لقد أضفت دعم getpass لنظام Linux ، بفضل هذه التوصية . أضفت أيضًا تنسيقًا للنص في الجهاز ، وذلك بفضل تسلسلات الهروب التي نسختها من مصدر Go الأصلي.



مكتبات



نظرًا لقلة فهمي لمخطط الرسم البياني ، لم يكن لدي أي فكرة من أين أبدأ. قادني البحث عن "graphql" على code.dlang.org إلى المكتبة المقابلة ، المسماة " Graphqld ". ومع ذلك ، بعد دراسته ، بدا لي أنه يبدو وكأنه مكون إضافي vibe d أكثر من كونه عميل حقيقي ، إن وجد.



بعد فحص طلبات الشبكة في Firefox ، أدركت أنه بالنسبة لهذا المشروع يمكنني ببساطة محاكاة طلبات وتحويلات الرسم البياني التي سأرسلها باستخدام عميل HTTP. الردود هي مجرد كائنات JSON يمكنني تحليلها باستخدام الأدوات التي توفرها الحزمة std.json. مع وضع هذا في الاعتبار ، بدأت في البحث عن عملاء HTTP واستقرت على الطلبات ، وهو عميل HTTP سهل الاستخدام ، ولكن الأهم من ذلك ، أنه وصل إلى مستوى معين من النضج.



لقد قمت بنسخ الطلبات الصادرة من محلل الشبكة ولصقتها في ملفات .graphql منفصلة ، ثم قمت باستيرادها وإرسالها بالمتغيرات المناسبة. تم وضع معظم الوظائف في بنية GraphQLRequest لأنني أردت إدراج نقاط النهاية والتكوينات المختلفة فيها حسب الحاجة للمشروع:



مصدر
struct GraphQLRequest
{
    string operationName;
    string query;
    JSONValue variables;
    Config configuration;

    JSONValue toJson()
    {
        return JSONValue([
            "operationName": JSONValue(operationName),
            "variables": variables,
            "query": JSONValue(query),
        ]);
    }

    string toString()
    {
        return toJson().toPrettyString();
    }

    Response send()
    {
        auto request = Request();
        request.addHeaders(["Authorization": configuration.get("token", "")]);
        return request.post(
            configuration.get("endpoint"),
            toString(),
            "application/json"
        );
    }
}




هنا مقتطف حزمة الصرف. يعالج الرمز التالي المصادقة:
struct Session
{
    Config configuration;

    void login(string username, string password)
    {
        auto request = createSession(username, password);
        auto response = request.send();
        response.throwOnFailure();
        string token = response.jsonBody
            ["data"].object
            ["createSession"].object
            ["token"].str;
        configuration.put("token", token);
    }

    GraphQLRequest createSession(string username, string password)
    {
        enum query = import("createSession.graphql").lineSplitter().join("\n");
        auto variables = SessionPayload(username, password).toJson();
        return GraphQLRequest("createSession", query, variables, configuration);
    }
}

struct SessionPayload
{
    string email;
    string password;

    //todo : make this a template mixin or something
    JSONValue toJson()
    {
        return JSONValue([
            "email": JSONValue(email),
            "password": JSONValue(password)
        ]);
    }

    string toString()
    {
        return toJson().toPrettyString();
    }
}




تنبيه المفسد - لم أفعل هذا من قبل.



يحدث كل شيء على هذا النحو: تقوم الوظيفة main () بإنشاء بنية تكوين من وسيطات سطر الأوامر وتدخلها في بنية الجلسة ، والتي تنفذ وظائف أوامر تسجيل الدخول والخروج والحالة. تنشئ طريقة createSession () استعلامًا في GraphQL من خلال قراءة الاستعلام الفعلي من ملف .graphql المقابل وتمرير المتغيرات معه. لم أرغب في تلويث شفرة المصدر الخاصة بي بطفرات واستعلامات في GraphQL ، لذلك قمت بنقلها إلى ملفات .graphql ، والتي استوردها بعد ذلك في وقت التجميع باستخدام enum and import. يتطلب الأخير علامة مترجم للإشارة إلى stringImportPaths (الذي يتم عرضه بشكل افتراضي /).



بالنسبة إلى طريقة تسجيل الدخول () ، فإن مسؤوليتها الوحيدة هي إرسال طلب HTTP ومعالجة الاستجابة. في هذه الحالة ، يتعامل مع الأخطاء المحتملة ، وإن لم يكن بحذر شديد. ثم يخزن الرمز المميز في ملف التكوين ، والذي هو في الحقيقة ليس أكثر من كائن JSON لطيف.



لا يعد أسلوب throwOnFailure جزءًا من الوظيفة الأساسية لمكتبة الاستعلام. إنها في الواقع وظيفة مساعدة تقوم ببعض معالجة الأخطاء السريعة والقذرة:



void throwOnFailure(Response response)
{
    if(!response.isSuccessful || "errors" in response.jsonBody)
    {
        string[] errors = response.errors;
        throw new RequestException(errors.join("\n"));
    }
}


نظرًا لأن D يدعم UFCS ، يمكن إعادة كتابة بناء جملة throwOnFailure (استجابة) كرد .throwOnFailure (). هذا يجعل من السهل التضمين في استدعاءات الطريقة الأخرى مثل send (). ربما كنت أفرط في استخدام هذه الوظيفة طوال المشروع.



معالجة الخطأ



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



string token = response.jsonBody
    ["data"].object
    ["createSession"].object
    ["token"].str;
configuration.put("token", token);


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



كان التعامل مع أخطاء نمط الصدأ كما هو موضح في المنشور الأصلي مثيرًا للاهتمام. لا أعتقد أن هناك أي شيء من هذا القبيل في مكتبة D القياسية ، ولكن كانت هناك مناقشات حول تطبيق هذا كمكتبة طرف ثالث.



مآخذ ويب



أريد فقط أن أوضح بإيجاز أنني لم أستخدم مآخذ الويب لتنفيذ أمر الساعة. حاولت استخدام عميل websocket من Vibe.d ولكنه لا يعمل مع واجهة hashtrack الخلفية لأنه استمر في إغلاق الاتصال. في النهاية ، تخلت عنها لصالح جولة روبن ، على الرغم من أنها مرفوضة. كان العميل يعمل منذ أن اختبرته مع خادم ويب آخر ، لذلك قد أعود إلى هذا في المستقبل.



التكامل المستمر



بالنسبة إلى CI ، قمت بإعداد وظيفتي إنشاء: إنشاء فرع عادي وإصدار رئيسي لضمان تنزيل الإنشاءات المحسّنة من العناصر الأثرية.









تقريبا. تظهر الصور وقت التجميع. مع مراعاة تحميل التبعيات. إعادة البناء بدون تبعيات ~ 4 ثانية



استهلاك الذاكرة



لقد استخدمت الأمر / usr / bin / time -v ./hashtrack --list لقياس استخدام الذاكرة كما هو موضح في منشور المدونة الأصلي. لا أعرف ما إذا كان استخدام الذاكرة يعتمد على علامات التجزئة التي يتابعها المستخدم ، ولكن فيما يلي نتائج برنامج D الذي تم تجميعه باستخدام إصدار dub build -b:

الحجم الأقصى للمقيمين (كيلو بايت): 10036

الحجم الأقصى للمقيمين (كيلو بايت): 10164

الحجم الأقصى للمجموعة السكنية (كيلو بايت): 9940

الحجم الأقصى للمقيمين (كيلو بايت): 10060

الحجم الأقصى للمقيمين (كيلو بايت): 10008


ليس سيئا. قمت بتشغيل إصدارات Go and Rust مع مستخدم hashtrack وحصلت على هذه النتائج:



Go build -ldflags "-s -w":

الحجم الأقصى للمقيمين (كيلو بايت): 13684 الحجم الأقصى للمقيمين ( كيلو

بايت): 13820

الحجم الأقصى للمقيمين (كيلو بايت): 13904

الحجم الأقصى للمقيمين (كيلو بايت): 13796

الحجم الأقصى للمقيمين (كيلو بايت): 13600

الصدأ المتراكم مع بناء البضائع - إطلاق:

الحجم الأقصى للمقيمين (كيلو بايت): 9224

الحجم الأقصى للمقيمين (كيلو بايت): 9192

الحجم الأقصى للمقيمين (كيلو بايت): 9384

الحجم الأقصى للمقيمين (كيلو بايت): 9132

الحجم الأقصى للمقيمين (كيلو بايت): 9168
التحديث: أوصى مستخدم Reddit skocznymroczny باختبار مجمعي LDC و GDC أيضًا. فيما يلي النتائج:

LDC 1.22 تم تجميعه بواسطة dub build -b release - compiler = ldc2 (بعد إضافة إخراج اللون و getpass)

الحجم الأقصى للمقيمين (كيلو بايت): 7816

الحجم الأقصى للمقيمين (كيلو بايت): 7912

الحجم الأقصى للمجموعة السكنية (كيلو بايت): 7804

الحجم الأقصى للمقيمين (كيلو بايت): 7832

الحجم الأقصى للمقيمين (كيلو بايت): 7804


يحتوي D على مجموعة القمامة ، ولكنه يدعم أيضًا المؤشرات الذكية ، ومؤخرًا منهجية إدارة الذاكرة التجريبية المستوحاة من Rust. لست متأكدًا تمامًا من مدى تكامل هذه الوظائف مع المكتبة القياسية ، لذلك قررت السماح لـ GC بمعالجة الذاكرة من أجلي. أعتقد أن النتائج جيدة بالنظر إلى أنني لم أفكر في استهلاك الذاكرة أثناء كتابة الكود.



حجم الثنائيات



Rust, cargo build --release: 7.0M



D, dub build -b release: 5.7M



D, dub build -b release --compiler=ldc2: 2.4M



Go, go build: 7.1M



Go, go build -ldflags "-s -w": 5.0M


.. — , , . Windows dub build -b release 2 x64 ( 1.5M x86-mscoff) , Rust Ubuntu18 - openssl, ,





أعتقد أن D هي لغة موثوقة لكتابة أدوات سطر الأوامر مثل هذه. لم أذهب إلى التبعيات الخارجية كثيرًا لأن المكتبة القياسية تحتوي على معظم ما أحتاجه. أشياء مثل تحليل وسيطات سطر الأوامر ، والتعامل مع JSON ، واختبار الوحدة ، وإرسال طلبات HTTP (مع cURL ) كلها متوفرة في المكتبة القياسية. إذا كانت المكتبة القياسية تفتقر إلى ما تحتاج إليه ، فإن حزم الطرف الثالث موجودة ، ولكن أعتقد أنه لا يزال هناك مجال للتحسين في هذا المجال. من ناحية أخرى ، إذا لم يتم اختراع عقلية المعاهد الوطنية للصحة الخاصة بك هنا ، أو إذا كنت ترغب في إحداث تأثير بسهولة كمطور مفتوح المصدر ، فإنك بالتأكيد ستحب النظام البيئي D.



أسباب استخدام D



  • نعم



All Articles