[RUBY] Ein Git-ähnliches selbst erstelltes Befehlszeilentool, das Tab ergänzt

Einführung

Ah, es ist ärgerlich, den Browser zu starten. Ich war wieder einmal begeistert von der Erstellung von Befehlszeilen-Tools. Etwas wie das.

Dieses Mal werde ich einen Befehl definieren, damit er mit TAB gespeichert werden kann. Ich konnte keine gute Probe finden, also habe ich es während der Untersuchung gemacht [^ 2]

[^ 1]: REPL (Read-Eval-Print Loop) ist eine Eingabe- / Auswertungs- / Ausgabeschleife. Ein Interpreter, mit dem der Benutzer und der Interpreter Codeteile interaktiv ausführen können. Ich persönlich nenne es einen Cisco-Typ.

[^ 2]: Vor langer Zeit habe ich einen Artikel wie Autocomplete with my own command line tool geschrieben, aber es ist die "Ruby" -Version. Als kleiner Unterschied zum letzten Mal ist es diesmal wie "REPL" [^ 1]

Demo

Diesmal bis ich so etwas mache

ac.gif

Wenn es nur eine Befehlshierarchie gibt

Die Vorlage ist im Grunde so mit "Readline". ① wird von TAB aufgerufen und ② dreht sich um

Einstufiger Befehl


require "readline"

#Befehle für Abschlusskandidaten
pet_store = [ "pet", "store", "user"]

#① Wird aufgerufen, um die Tabulatortaste zu drücken und die Auswahl abzuschließen
Readline.completion_proc = proc do |input|
  pet_store.select { |name| name.start_with?(input) }
end

#② Drücken Sie zum Drehen die Eingabetaste
while input = Readline.readline("$ ", false)
  if input
    break if input == "q"
    p Readline.line_buffer.split if !input.empty?
  end
end

Bei einer Schicht ist dies einfach, da die Befehlskandidaten festgelegt sind

Wenn der Befehl mehrschichtig ist

Bei einem mehrschichtigen Befehl sollte der Befehl des Abschlusskandidaten im vorherigen Beispiel entsprechend dem aktuellen Kontext geändert werden (zuvor ausgewählter Befehl).

Der Punkt ist, dass der "nächste Kandidat" dynamisch gemäß dem "aktuell ausgewählten Befehl" ersetzt werden sollte. Definieren wir also so etwas wie einen Befehlsbaum. Da es sich um eine Baumstruktur handelt, versuchen Sie es mit YAML.

petstore.rb



require "readline"
require "yaml"

command_tree = <<-YAML
pet:
  buy:
    dog:
    cat:
  sell:
    bird:
    fox:
  list:
    all:
    filter_by:
store:
  find:
    by_name:
    by_tag:
    by_address:
  list:
user:
  login:
  loout:
  sign_up:
YAML

command_tree = YAML.load(command_tree)
#pp command_tree

#Wechseln Sie dynamisch "nächster Kandidat" gemäß "aktuell ausgewähltem Befehl" (gehen Sie den Baum hinunter)
def current_option(command_tree, line_buffer)
  current_option = command_tree.keys
  line_buffer.split.each do |command|
    command_tree = command_tree[command] 
    if command_tree.nil?
      current_option
    else
      current_option = command_tree.keys
    end
  end
  current_option
end

comp = proc do |input|
  current_option(command_tree, Readline.line_buffer).select { |name| name.to_s.start_with?(input) }
end

Readline.completion_proc = comp

while input = Readline.readline("$ ", true)
  break if input == "q"
  p Readline.line_buffer.split if !input.empty?
end

Was mache ich:

Es ist wie

Kleinere Korrektur

Es mag ein kleiner Fall sein, aber mit der obigen Methode wird der richtige Kandidat angezeigt, wenn mehrere Kandidaten noch übereinstimmen, selbst wenn die Befehlskandidaten bis zu "pet" wie "pet" und "petGroup" abgeschlossen sind. nicht. Dies möchte einen Kandidaten für "pet" oder "petGroup" finden, wenn bis "pet" abgeschlossen ist, aber es entspricht dem Hash-Schlüssel "pet" und geht eine Ebene tiefer, also unter "pet" Dies liegt daran, dass die Hierarchie ein Kandidat wird. Um dies zu vermeiden, habe ich Folgendes versucht: 1.


def get_current_option(command_tree, line_buffer)
  current_option = command_tree.keys
  commands = line_buffer.split

  commands.each_with_index do |command, i|

    # 1. Don't go down to the lower level(and return current_option) in case current command matches multiple candidates such as "pet" and "petGroup" 
    return current_option if i == commands.size-1 and !line_buffer.end_with?("\s")

    # 2. Go down  
    if command_tree.has_key?(command) 
      if command_tree[command].nil? # invalid command or key at leaf
        current_option = []
      else
        command_tree = command_tree[command] 
        current_option = command_tree.keys
      end
    end
  end
  current_option
end

Jetzt haben Sie das erwartete Verhalten und können bequem tippen.

von jetzt an

Nun, da es funktioniert, gehen wir zum Befehl Parsen und Verarbeiten. Wenn es eine bessere Implementierungsmethode für die Ergänzung gibt, geben Sie mir bitte einen Rat!

Referenzierte Site

Recommended Posts

Ein Git-ähnliches selbst erstelltes Befehlszeilentool, das Tab ergänzt
[Docker] Verwendung als Befehlszeilenprogramm durch Angabe von Argumenten (Wrapper-Skript)