۳۰ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

کنسول های تعاملی جاوا با JLine و ConsoleUI

با این نمایش عملی ساخت کنسول‌های رابط کاربری پیشرفته و REPL در جاوا وارد پوسته جاوا شوید.

با این نمایش عملی ساخت کنسول‌های رابط کاربری پیشرفته و REPL در جاوا وارد پوسته جاوا شوید.

رابط خط فرمان (CLI) دنیای درونی توسعه نرم افزار است. از پوسته، ما به تمام قابلیت‌های سیستم عامل دسترسی مستقیم داریم و به همراه آن، می‌توانیم تمام جنبه‌های نرم‌افزار را بسازیم و هماهنگ کنیم. بسیاری از ابزارها و چارچوب ها خطوط فرمان را در خود جای داده اند. نه تنها این، بلکه خط فرمان، جادوی اصلی کار با سیستم های نرم افزاری است. این خانه تقریباً نامحدود است.

در این مقاله، ما گشتی در ساخت برنامه‌های کاربردی رابط خط فرمان تعاملی پیچیده (CLI) و REPL (حلقه‌های خواندنی، چاپی یا پوسته‌های تعاملی) در جاوا خواهیم داشت. ما یک برنامه آزمایشی اولیه در جاوا راه اندازی می کنیم و از JLine و ConsoleUI کتابخانه ها برای اضافه کردن ویژگی های مورد نیاز.

REPL مبتنی بر جاوا

نمایش ما بر اساس یک برنامه کاربردی تئوری است که فهرست کار یک پروژه نرم افزاری را بررسی می کند و اطلاعات مربوط به پروژه ها را در آنجا جمع آوری می کند. این برنامه همچنین قادر به ایجاد پروژه های جدید در دایرکتوری است. برنامه مثال یک REPL را شروع می کند که دو دستور describe و create را می پذیرد که می تواند با برگه تکمیل شود. دستور describe سلسله‌مراتب پوشه دایرکتوری کاری را با کدگذاری رنگی (در صورت لزوم با استفاده از صفحه‌بندی) فهرست می‌کند، در حالی که create یک منوی تعاملی را آغاز می‌کند که به کاربر اجازه می‌دهد انتخاب کند چه نوع پروژه برای ایجاد – جاوا، جاوا اسکریپت یا پایتون. اگر این یک برنامه جاوا است، به چند انتخابی از ویژگی‌های اضافی که کاربر می‌تواند اضافه کند (پایگاه داده یا REST API) اجازه می‌دهیم تا یک منوی تودرتو ببینیم.

نحوه استفاده از الگوی واحد کار در ASP.NET Core

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

برنامه آزمایشی

برای این تور، به Java JDK و Maven نیاز دارید. ما با ایجاد یک برنامه جدید با کهن الگوی Maven، مانند آنچه در فهرست ۱ نشان داده شده است، شروع می کنیم.


mvn archetype:generate -DgroupId=com.infoworld -DartifactId=jline3 -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Maven از این دستورات برای طرح یک پروژه جدید برای ما استفاده خواهد کرد. قبل از اینکه ادامه دهیم، بیایید تمام وابستگی‌هایی را که نیاز داریم اضافه کنیم، و همچنین نسخه جاوا را روی ۱۱ تنظیم کنیم (هر نسخه از جاوا ۸ به جلو باید کار کند)، همانطور که در فهرست ۲ انجام دادم. این برای < صدق می‌کند. فایل code>pom.xml در ریشه پروژه (بقیه pom.xml را همانطور که هست رها کنید).


<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.8.1</version>
      <configuration>
        <source>11</source>
        <target>11</target>
      </configuration>
    </plugin>
  </plugins>
</build>
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.1</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.jline</groupId>
    <artifactId>jline</artifactId>
    <version>3.16.0</version>
  </dependency>
  <dependency>
    <groupId>org.fusesource.jansi</groupId>
    <artifactId>jansi</artifactId>
    <version>2.4.0</version>
  </dependency>
  <dependency>
    <groupId>org.jline</groupId>
    <artifactId>jline-terminal-jansi</artifactId>
    <version>3.20.0</version>
  </dependency>
</dependencies>

بعد، بیایید کلاس اصلی را در src/main/java/com/infoworld/App.java تغییر دهیم تا یک حلقه REPL شروع شود. App.java را با استفاده از کد موجود در فهرست ۳ تغییر دهید.


package com.infoworld;
import org.jline.reader.*;
import org.jline.reader.impl.*;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.completer.*;
import org.jline.terminal.TerminalBuilder;
import org.jline.terminal.Terminal;

import java.io.IOException;
import java.util.*;

public class App {

    public static void main(String[] args) throws IOException {
        Terminal terminal = TerminalBuilder.terminal();
        LineReader reader = LineReaderBuilder.builder()
                .terminal(terminal)
                .completer(new StringsCompleter("describe", "create"))
                .build();

        while (true) {
            String line = reader.readLine("> ");
            if (line == null || line.equalsIgnoreCase("exit")) {
                break;
            }
            reader.getHistory().add(line);
            System.out.println("You said: " + line);
        }
    }
}

لیست ۳ یک برنامه بسیار ساده ایجاد می کند که خطوط ورودی کاربر را مشاهده می کند و آنها را بازتاب می دهد. به آن، یک «تکمیل‌کننده» اضافه کردم که دو فرمان ما را پشتیبانی می‌کند، describe و create. این بدان معناست که وقتی کاربر در حال تایپ کردن در اعلان است، می‌تواند برای تکمیل این دستورات، برگه‌ها را بزند. دوبار Tabb کردن منویی با دستورات موجود ارائه می دهد. JLine با فراخوانی روش .completer(new StringsCompleter("describe", "create")) این کار را بسیار آسان کرده است. JLine علاوه بر Strings چندین تکمیل کننده داخلی دارد و شما همچنین می توانید خودتان را بسازید.

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

در قلب، REPL یک حلقه while نامحدود است که با ورود کاربر به خروج می‌شکند.

می توانید با اجرای دستور Maven exec:java نشان داده شده در فهرست ۴ آن را آزمایش کنید.


mvn clean package exec:java -Dexec.mainClass=com.infoworld.App

اعلام هویج، پاسخ اکو، و دستورات تکمیل برگه را دریافت خواهید کرد.

بررسی دستورات REPL

اکنون که echo REPL با تکمیل خودکار کار می کند، اجازه دهید در واقع دستورات را مدیریت کنیم. ما این کار را با جاوا معمولی، با مقایسه رشته وارد شده با دستورات و روش های فراخوانی برای هر یک انجام خواهیم داد. در حال حاضر، create کاری انجام نمی دهد، اما ما منطق را برای خروجی سلسله مراتب دایرکتوری، همانطور که در لیست ۵ نشان داده شده است، پیاده سازی می کنیم.


package com.infoworld;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.jline.reader.LineReader;
import org.jline.reader.impl.completer.StringsCompleter;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;

public class App {
  public static void main(String[] args) throws IOException {
    Terminal terminal = TerminalBuilder.terminal();

    LineReader reader =
        LineReaderBuilder.builder()
            .terminal(terminal)
            .completer(new StringsCompleter("describe", "create"))
            .parser(new DefaultParser())
            .build();

    while (true) {
      String line = reader.readLine("> ");
      if (line == null || line.equalsIgnoreCase("exit")) {
        break;
      }
      reader.getHistory().add(line);

      if (line.equalsIgnoreCase("describe")) {
        Path path = Paths.get(".");
        System.out.println(getDirectoryHierarchy(path));
      } else if (line.equalsIgnoreCase("create")) {
        System.out.println(“TBD”);
      } else {
        System.out.println("Unknown command: " + line);
      }
    }
  }

  public static String getDirectoryHierarchy(Path path) {
    StringBuilder sb = new StringBuilder();
    try (Stream<Path> paths = Files.walk(path)) {
      paths.sorted()
          .forEach(
              p -> {
                int depth = path.relativize(p).getNameCount();
                for (int i = 0; i < depth; i++) {
                  sb.append(" ");
                }
    if (p.toFile().isDirectory()) {
                  sb.append("/"); 
                }
                sb.append(p.getFileName()).append("\n");
              });
    } catch (IOException e) {
      e.printStackTrace();
    }
    return sb.toString();
  }
}

اکنون هنگامی که برنامه را اجرا می کنید، اگر دستور describe را وارد کنید، فهرستی با فرمت تورفتگی از دایرکتوری کاری را خواهید دید. کار ساخت آن رشته در getDirectoryHierarchy() اتفاق می افتد. این روش از جاوای معمولی از بسته java.nio.file استفاده می‌کند تا دایرکتوری را راه‌اندازی کند و هر فایل و دایرکتوری را خروجی دهد، و برای هر سطح از دایرکتوری که پایین می‌رویم یک فاصله ایجاد می‌کند. این کار عمدتاً با path.relativize(p).getNameCount() انجام می شود که می گوید: از مسیر فعلی من (.) مسیر نسبی به مسیر فعلی را به من بدهید. (به عنوان مثال، ./src/main/java). getNameCount() فقط تعداد نام‌های موجود در آن مسیر را می‌شمارد – در این مورد، سه نام. برای هر نام، یک فاصله اضافه می کنیم.