الوحدة REPL في Node.js

من موسوعة حسوب

الاستقرار: 2-مستقر

تقدّم الوحدة repl تطبيق قراءة وتقييم وطباعة حلقة تكرار والذي يكون متوافرًا كبرنامج بحد ذاته (مستقل) أو مُتَضمّن في تطبيق آخر. يمكن الوصول إليه باستخدام:

const repl = require('repl');

التصميم والميزات

تُصدِّر الوحدة repl الصنف repl.REPLServer أثناء التنفيذ، سوف تقبل نسخ repl.REPLServer أسطرًا مفردةً من دخل المستخدم وتقيّمها تبعًا لدوال تقييم معرّفة من المستخدم ومن ثمّ تخرج الناتج. ربما يكون الدخل والخرج من stdin و stdout، على التوالي، ربما تكون متصلة بأي مجرى (stream) يتبع لبرمجية Node.js.

تدعم نُسخ repl.REPLServer الإكمال التلقائي للدخل، وتعديل سطري بنمط برمجية Emacs‏ (Emacs-style) البسيط، ودخل متعدد الأسطر، و خرج بأسلوب ANSI-styled، وحفظ واستعادة حالة جلسة REPL الحالية، وإصلاح الأخطاء، ودوال تقييم قابلة للتخصيص.

أوامر ومفاتيح خاصة

الأوامر الخاصة التالية مدعومة من قبل كل نُسخ  REPL:

  • .break: في عملية إدخال تعبير متعدد الأسطر، سوف يوقف إدخال الأمر ‎.break (أو الضغط على مجموعة المفاتيح ‎<ctrl>‎-‎C‎) الدخل الإضافي أو معالجة ذاك التعبير.
  • ‎.clear: يعيد ضبط قيمة REPL context إلى كائن فارغ ويمسح أي تعابير متعددة الأسطر تُدخل حاليًا.
  • ‎.exit: يغلق مجرى الدخل/الخرج (I/O)، متسبّبًا بخروج REPL.
  • ‎.help: يظهر هذه القائمة من الأوامر الخاصة.
  • ‎.save: يحفظ جلسة REPL الحالية إلى ملف: ‎>‎ ‎.save ./file/to/save.js‎
  • ‎.load: يحمّل ملفًا إلى جلسة REPL الحالية. ‎> ‎.‎load ./file/to/load.js
  • ‎.editor: يدخل وضع التعديل (للانتهاء ‎<ctrl>‎-‎D‎، وللإلغاء ‎<ctrl>‎-‎C)
> .editor


 //( ^C وللإلغاء ^D دخول وضع التعديل (للانتهاء 

function welcome(name) {
  return `Hello ${name}!`;
}

welcome('Node.js User');

// ^D
'Hello Node.js User!'
>

يملك المزيج التالي من المفاتيح في REPL هذه التأثيرات الخاصة:

  • <ctrl>‎‎-‎‎C‎: عندما تُضغط مرّة، فإنها تملك ذات التأثير كأمر ‎.break .عندما تُضغط مرّتين على سطر فارغ، تملك ذات تأثير أمر ‎.exit
  • <ctrl>‎-‎D: تملك ذات تأثير أمر ‎.exit
  • ‎<tab>‎: عندما تُضغط على سطر فارغ، تعرض المتحولات العامة والمحلية (النطاق). عندما تُضغط أثناء ادخال دخل آخر، تعرض خيارات إكمال تلقائي ذات صلة.

التقييم الإفتراضي

بشكل افتراضي، تستخدم كل نسخ repl.REPLServer دالة تقييم والتي تقيّم تعابير JavaScript وتوفّر وصول إلى وحدات Node.js مُدمجة. يمكن أن يُعاد تعريف هذا السلوك الافتراضي بتمرير دالة تقييم بديلة عندما تُنشأ نسخة repl.REPLServer.

تعابير JavaScript

يدعم المقيّم الافتراضي تقييم مباشر لتعابير JavaScript:

> 1 + 1
2
> const m = 2
undefined
> m + 1
3

إلّا إذا وسِّع من خلال كتل برمجية أو دوال، يُصرّح عن المتحولات المصرّح عنها في النطاق العام إما بشكل ضمني أو باستخدام الكلمات المفتاحية const أو let أو var.

النطاق العام والمحلي

يوفّر المقيّم الافتراضي وصولًا إلى أي متحول موجود في النطاق العام. من الممكن استخراج متحول إلى REPL بشكل صريح بإسناده إلى كائن context مترافق مع كل REPLServer:

const repl = require('repl');
const msg = 'message';

repl.start('> ').context.m = msg;

تظهر الخاصيات في كائن context كمحلية ضمن REPL:

$ node repl_test.js
> m
'message'

خاصيات السياق Context افتراضيًا ليست للقراءة-فقط، لتحديد قراءة-فقط على النطاق العام، يجب أن تُعرّف خاصيات السياق باستخدام Object.defineProperty()‎:

const repl = require('repl');
const msg = 'message';

const r = repl.start('> ');
Object.defineProperty(r.context, 'm', {
  configurable: false,
  enumerable: true,
  value: msg
});
الوصول إلى نواة وحدات Node.js

سوف يحمّل المقيّم الافتراضي تلقائيًا الوحدات الأساسية في Node.js إلى بيئة REPL عندما تُستخدم. على سبيل المثال، إلّا إذا صرّح عنها كمتحولات عامة أو متحولات نطاق. سوف يُقيّم الدخل fs عند الطلب كما يلي ‎global.fs = require('fs')‎:

> fs.createReadStream('./some/file');
الاستثناءات العامة غير الملتقطة

يستخدم REPL وحدة domain لإلتقاط كل الاستثناءات غير المُلتقطة من أجل جلسة REPL تلك.

يشتمل استخدام وحدة domain في REPL على هذه الآثار الجانبية:

إسناد المتحول '_' (underscore)

سجل التغييرات

الإصدار التغييرات
التغييرات أضيف دعم ‎_‎error

بشكل افتراضي، سوف يسند المقيّم الافتراضي قيمة أحدث تعبير مُقيّم إلى المتحول الخاص _ (شرطة سفلية). ضبط _ بشكل صريح إلى قيمة ما سوف يعطّل هذا السلوك.

> [ 'a', 'b', 'c' ]
[ 'a', 'b', 'c' ]
> _.length
3
> _ += 1
Expression assignment to _ now disabled.
4
> 1 + 1
2
> _
4

بشكل مشابه، سوف يشير ‎_‎error‎ إلى آخر خطأ مُشاهد، إذا وُجِد. ضبط ‎_‎error‎ إلى قيمةٍ ما بشكل صريح سوف يعطّل هذا السلوك.

> throw new Error('foo');
Error: foo
> _error.message
'foo'
الكلمة المفتاحية await

مع خيار سطر الأوامر المُحدد ‎--experimental‎-repl-await‎ ، مُكّن الدعم التجريبي للكلمة المفتاحية await.

> await Promise.resolve(123)
123
> await Promise.reject(new Error('REPL await'))
Error: REPL await
    at repl:1:45
> const timeout = util.promisify(setTimeout);
undefined
> const old = Date.now(); await timeout(1000); console.log(Date.now() - old);
1002
undefined

دوال تقييم مُخصصة

عندما تُنشأ نسخة جديدة من الكائن repl.REPLServer جديدة، ربما تُقدّم دوال تقييم مخصصة. يمكن أن يستخدم هذا على سبيل المثال، لتنفيذ تطبيقات REPL مخصصة بشكل كامل.

يشرح التالي مثال REPL نظري والذي ينجز ترجمةً لنص من لغة إلى أخرى:

const repl = require('repl');
const { Translator } = require('translator');

const myTranslator = new Translator('en', 'fr');

function myEval(cmd, context, filename, callback) {
  callback(null, myTranslator.translate(cmd));
}

repl.start({ prompt: '> ', eval: myEval });
أخطاء يمكن إصلاحها

بما أنَّ المستخدم يكتب المدخلات ضمن مِحَث REPL، سوف يرسل ضغطُ المفتاح ‎<enter>‎ سطرَ المدخلات الحالية إلى دالة eval. بغية دعم المدخلات متعدد الأسطر، يمكن أن تعيد الدالة evalنسخة من الصنف repl.Recoverable إلى دالة رد النداء المتوافرة:

function myEval(cmd, context, filename, callback) {
  let result;
  try {
    result = vm.runInThisContext(cmd);
  } catch (e) {
    if (isRecoverableError(e)) {
      return callback(new repl.Recoverable(e));
    }
  }
  callback(null, result);
}

function isRecoverableError(error) {
  if (error.name === 'SyntaxError') {
    return /^(Unexpected end of input|Unexpected token)/.test(error.message);
  }
  return false;
}

تخصيص خرج REPL

افتراضيًا، تنسِّق النسخ repl.REPLServer الخرج باستخدام التابع util.inspect()‎ قبل كتابة الخرج إلى المجرى Writable المُقدّم (يكون process.stdout افتراضيًا). يمكن أن يُحدَّد الخيار useColors المنطقي عند البناء ليأمر الكاتب الافتراضي باستخدام النمط ANSI المستعمل في تنسيق الشيفرات لتلوين خرج التابع util.inspect()‎.

من الممكن تخصيص خرج النسخة repl.REPLServer بشكل كامل عن طريق تمرير دالة جديدة إليه باستخدام الخيار writer عند البناء. على سبيل المثال، يحوّل المثال التالي أي نص مدخل إلى حالة الأحرف الكبيرة:

const repl = require('repl');

const r = repl.start({ prompt: '> ', eval: myEval, writer: myWriter });

function myEval(cmd, context, filename, callback) {
  callback(null, cmd);
}

function myWriter(output) {
  return output.toUpperCase();
}

الصنف REPLServer

أضيف في الإصدار: 0.1.91

يرث الصنف repl.REPLServer من الصنف readline.Interface. تُنشَأ النُسخ repl.REPLServer باستخدام التابع repl.start()‎ ولا ينبغي أن تُنشَأ بشكل مباشر باستخدام كلمة JavaScript المفتاحية new.

الحدث 'exit'

أضيف في الإصدار: 0.7.7.

يُطلق الحدث 'exit' عندما يُنهَى REPL إمّا باستقبال أمر ‎.exit‎ كدخل، أو بضغط المستخدم على المفتاحين ‎<ctrl>‎-‎C مرتين لإرسال الإشارة SIGINT، أو بضغط المفتاحين ‎<ctrl>‎-‎D لإطلاق الحدث 'end' في مجرى الدخل. سيُستدعى تابع  رد نداء المستمع (listener callback) دون أي وسائط.

replServer.on('exit', () => {
  console.log('Received "exit" event from repl!');
  process.exit();
});

الحدث: 'reset'

أضيف في الإصدار: 0.11.0

يُطلق الحدث 'reset' عندما يُعاد ضبط سياق REPL. يحصل هذا عندما يُستقبَل الأمر ‎.clear‎ كدخل إلّا إذا كانت REPL تستخدم المقيّم (evaluator) الإفتراضي وأُنشأَت نسخة من الصنف repl.REPLServer مع ضبط الخيار useGlobal إلى القيمة true. ستُستدعى دالة رد نداء المستمع مع مرجعٍ يشير إلى كائن context، إذ يعدُّ هذا المرجع وسيطها الوحيد.

يمكن أن يُستخدم هذا في المقام الأول لإعادة تهيئة سياق REPL لبعض الحالات المحددة مسبقًا:

const repl = require('repl');

function initializeContext(context) {
  context.m = 'test';
}

const r = repl.start({ prompt: '> ' });
initializeContext(r.context);

r.on('reset', initializeContext);

عندما تُنفّذ هذه الشيفرة، يمكن أن يُعدَّل المتغير 'm' العام ولكن يعاد بعد ذلك لقيمته الإبتدائية باستخدام الأمر ‎.clear‎:

$ ./node example.js
> m
'test'
> m = 1
1
> m
1
> .clear
Clearing context...
> m
'test'
>

replServer.defineCommand(keyword, cmd)‎

أضيف في الإصدار: 0.3.0.

  • keyword: ‏‎<string>‎ الكلمة المفتاحية للأمر (دون المحرف . الافتتاحي).
  • Cmd:‏ ‎<Object>‎ | <Function>‎ الدالة المراد استدعاؤها عندما يُعالَج الأمر.

يُستخدَم التابع replServer.defineCommand()‎ لإضافة أمر جديد مسبوق بنقطة . إلى نسخة REPL. تُستدعى مثل هذه الأوامر بكتابة . متبوعة بالكلمة keyword. تكون cmd إمّا Function أو Object مع الخاصيات التالية:

  • help:‏ <string> نص مساعدة المراد عرضه عندما يُكتَب ‎.help (اختياري).
  • action:‏ <Function> الدالة المراد تنفيذها. تقبل اختياريًا سلسلة نصية كوسيط وحيد.

يُظهِر المثال التالي أمرين جديدين مضافين إلى نسخة REPL:

const repl = require('repl');

const replServer = repl.start({ prompt: '> ' });
replServer.defineCommand('sayhello', {
  help: 'Say hello',
  action(name) {
    this.clearBufferedCommand();
    console.log(`Hello, ${name}!`);
    this.displayPrompt();
  }
});
replServer.defineCommand('saybye', function saybye() {
  console.log('Goodbye!');
  this.close();
});

يمكن أن تُستخدم هذه التعليمات من داخل النسخة REPL نفسها:

> .sayhello Node.js User
Hello, Node.js User!
> .saybye
Goodbye!

replServer.displayPrompt([preserveCursor‎]‎)‎

أضيف في الإصدار: 0.1.91.

يُعِدُّ التابع replServer.displayPrompt()‎ نسخة REPL لاستقبال المدخلات من المستخدم، ثم طباعة prompt بعد ضبطه إلى سطر جديد في الخرج output ثم استئناف الدخل input لقبول مدخلات جديدة.

عندما تكون المدخلات متعدد الأسطر، يُطبع علامة الحذف (...) بدلًا من 'prompt'.

عندما تكون قيمة preserveCursor هي true، لن يعاد ضبط موضع المؤشر إلى 0.

الغرض من التابع replServer.displayPrompt في المقام الأول هو استدعاؤه من داخل دالة الإجراء (action function) لأجل الأوامر المُسجلَة باستخدام التابع replServer.defineCommand()‎.

replServer.clearBufferedCommand()‎

أضيف في الإصدار: 9.0.0.

يمسح التابع replServer.clearBufferedCommand()‎ أي أوامر خُزّنت مؤقتًا ولكنَّها لم تُنفّذ بعد. الغرض من هذا التابع في المقام الأول هو استدعاؤه من داخل دالة الإجراء (action function) لأجل الأوامر المُسجلَة باستخدام التابع replServer.defineCommand()‎.

replServer.parseREPLKeyword(keyword[, rest]‎)

الاستقرار: 0-مهمل.

أضيف في الإصدار: 0.8.9.أهمل منذ الإصدار: 9.0.0.

  • keyword:‏ <string> الكلمة المفتاحية المرتقبة للتحليل والتنفيذ.
  • rest:‏ <any> أي معاملات للكلمة المفتاحية keyword.
  • القيمة المعادة: ‎<boolean>‎

تابع داخلي يُستخدَم لتحليل وتنفيذ كلمات REPLServer المفتاحية. يعيد القيمة  true إذا كانت الكلمة المفتاحية keyword صالحة، وإلّا سيعيد القيمة false.

repl.start([options]‎)‎

سجل التغييرات

الإصدار التغييرات
10.0.0 أزيل الخيار REPL_MAGIC_MODE replMode
5.8.0 أصبح المعامل options اختياريًا الآن.
0.1.91 أُضيف هذا التابع.
  • options:‏ ‎ <Object>‎ | <string>‎‎
    • prompt:‏ <string> مِحثّ الإدخال المراد إظهاره. القيمة الإفتراضية: ‎'‎>‎ '‎‎ (مع مسافة زائدة)
    • input:‏ <stream.Readable> المجرى Readable الذي سيُقرأ منه دخل REPL. القيمة الإفتراضية: process.stdin.
    • output:‏ <stream.Writable> المجرى Writable الذي سيكتب إليه خرج REPL. القيمة الافتراضية: process.stdout.
    • terminal:‏ <boolean> قيمة منطقية إذا كانت true، تحدِّد أنّ output ينبغي أن يُعامل كطرفية TTY، ويمتلك شيفرات هروب ANSI/VT100 مكتوبة له. القيمة الافتراضية له تعتمد على قيمة الخاصية isTTY على المجرى output.
    • eval:‏ <Function> الدالة التي ستُستخدم عند تقييم كل سطر مُعطَى من الدخل. القيمة الإفتراضية هي: مغلف (wrapper) غير متزامن للدالة eval()‎ الموجودة في JavaScript. يمكن أن تخطئ دالة eval مع repl.Recoverable لتشير إلى أنَّ الدخل غير مُكتمل وتُظهر أسطر إضافية.
    • useColors:‏ <boolean> قيمة منطقية إذا كانت true، تشير إلى أنَّ الدالة writer الافتراضية ينبغي أن تتضمن نمط الألوان ANSI في خرج REPL. إذا أعطيت دالة writer مُخصَّصة، فليس لهذا الخيار عند ذلك أي تأثير. القيمة الإفتراضية: قيمة النُسَخ terminal في REPL.
    • useGlobal:‏ <boolean> قيمة منطقية إذا كانت true، فستشير إلى أنّ تابع التقييم الافتراضي سيستخدم global التي تخص JavaScript كسياق في مقابل إنشاء سياق منفصل جديد لنسخة REPL. تضبط العقدة CLI REPL هذه القيمة إلى true. القيمة الافتراضية: false.
    • ignoreUndefined:‏ <boolean> قيمة منطقية إذا كانت true، فستشير إلى أنَّ الكاتب الافتراضي لن يُظهِر قيمة الأمر المُعادة إذا قُيّمت على أنَّها غير معرّفة (undefined). القيمة الافتراضية: false.
    • writer:‏ <Function> دالة يراد استدعاؤها لتنسيق المخرجات لكل أمر قبل كتابته إلى الخرج output.القيمة الافتراضية: util.inspect()‎.
    • completer:‏ <Function> دالة اختيارية تُستخدم للإكمال التلقائي عبر المفتاح Tap. انظر التابع readline.InterfaceCompleter على سبيل المثال.
    • replMode:‏ <symbol> راية تحدِّد فيما اذا كان المقيّم الافتراضي ينفذ كل أوامر JavaScript في النمط الصارم أو النمط الافتراضي (المتساهل). القيم المقبولة لاستعمالها مع الخيار هي:
      • repl.‎REPL_MODE_SLOPPY‎: تقيّم التعابير في النمط المتساهل.
      • repl.‎REPL_MODE_STRICT‎: تقيّم التعابير في النمط الصارم. هذا مكافئ لاستهلال كل عبارة repl بالعبارة 'use strict'.
    • breakEvalOnSigint‎: يوقف تقييم القطعة الحالية من الشيفرة عند استقبال الإشارة SIGINT، أي ضُغِطَ المفتاحان Ctrl+C. لايمكن أن يُستخدَم هذا الخيار في نفس الوقت مع الخيار eval. القيمة الافتراضية هي: false.

يُنشئ التابع repl.start()‎ ويبدأ النسخة repl.REPLServer.

إذا كان المعامل options سلسلةً نصيةً (string)، فسيُحدِّد محثّ الإدخال:

const repl = require('repl');

// نمط محث يونيكس
repl.start('$ ');

الوحدة REPL في Node.js  

تستخدم Node.js بحدِّ ذاتها الوحدة repl لتوفير واجهة تفاعلية لها لتنفيذ JavaScript. يمكن أن يستخدم هذا عن طريق تنفيذ شيفرة Node.js التنفيذية دون تمرير أي وسائط (أو بتمرير الوسيط ‎-i‎):

$ node
> const a = [1, 2, 3];
undefined
> a
[ 1, 2, 3 ]
> a.forEach((v) => {
...   console.log(v);
...   });
1
2
3

خيارات متحولات البيئة

يمكن تخصيص سلوكيات الوحدة REPL في Node.js باستخدام متغيرات البيئة التالية:

  • NODE_REPL_HISTORY: عندما يُعطى مسار صالح، سيُحفظ تاريخ REPL الدائم إلى الملف الذي يشير إليه هذا المسار بدلًا من حفظه في المجلد ‎‎.‎node_repl_history‎ في المجلد home للمستخدم. ضبط هذه القيمة إلى ' ' سوف يعطّل تاريخ REPL الدائم. ستُزال المسافات البيضاء من القيمة.
  • NODE_REPL_HISTORY_SIZE: يتحكم بعدد أسطر التاريخ (history) التي ستُحفَظ اذا كان التاريخ متوافرًا. يجب أن تكون عددًا موجبًا. القيمة الافتراضية هي: 1000
  • NODE_REPL_MODE: إمّا أن تكون 'sloppy' أو 'strict'. القيمة الافتراضية: 'sloppy'، والتي ستسمح بتنفيذ شيفرات النمط غير الصارم.

التاريخ الدائم

افتراضيًّا، ستبقي Node.js REPL التاريخ بين جلسات node REPL عن طريق حفظ المدخلات إلى المف ‎.‎node_repl_history المتوضع في المجلد home للمستخدم. يمكن أن يُعطَّل ذلك بضبط متغير البيئة بالشكل NODE_REPL_HISTORY‎=‎''‎.

استخدام Node.js REPL مع محرر سطري متقدم

من أجل محررات الأسطر المتقدّمة، ابدأ Node.js مع متغير البيئة NODE_NO_READLINE=1 . هذا سوف يبدأ main والمنقّح (debugger)‏ الذي يخص REPL في إعدادات طرفية معيارية، والتي ستسمح بالاستخدام مع rlwrap.

فمثلًا، يمكن أن يُضاف السطر التالي إلى الملف ‎.‎bashrc‎:

alias node="env NODE_NO_READLINE=1 rlwrap node"

بدء عدّة نسخ REPL مقابل نسخة شغالة وحيدة

من الممكن إنشاء وتشغيل عدة نُسخ REPL مقابل نسخة Node.js شغّالة وحيدة والتي تشارك كائن global وحيد ولكن تمتلك عدة واجهات دخل/خرج (I/O).

على سبيل المثال، يوفر المثال التالي وحدات REPL مستقلة على مجرى الدخل القياسي stdin، ومقبس يونكس والمقبس TCP:

const net = require('net');
const repl = require('repl');
let connections = 0;

repl.start({
  prompt: 'Node.js via stdin> ',
  input: process.stdin,
  output: process.stdout
});

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via Unix socket> ',
    input: socket,
    output: socket
  }).on('exit', () => {
    socket.end();
  });
}).listen('/tmp/node-repl-sock');

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via TCP socket> ',
    input: socket,
    output: socket
  }).on('exit', () => {
    socket.end();
  });
}).listen(5001);

تشغيل هذا التطبيق من موجه الأوامر يؤدي إلى بدء REPL على مجرى الدخل القياسي (stdin). يمكن أيضًا أن يتصل عملاء REPL الآخرون عبر مقبس يونكس أو المقبس TCP.

على سبيل المثال، يفيد telnet في الاتصال بمقابس TCP، بينما يمكن أن يُستخدم socat للاتصال بمقبس يونكس ومقبس TCP كلاهما.

ببدء REPL من خادم يعتمد على مقبس يونكس بدلًا من مجرى الدخل القياسي، يمكن الاتصال بعملية Node.js شغالة لمدة طويلة دون إعادة تشغيلها.

مثالٌ عن تشغيل طرفيةREPL (terminal)‎ "كاملة المواصفات" عبر النسخة net.Server والنسخة net.Socket، تجده في هذه الصفحة. مثالٌ آخر عن تشغيل نسخة REPL عبر curl(1)‎، تجده في هذه الصفحة.

مصادر

صفحة REPL في توثيق Node.js الرسمي.