Ah, c'est ennuyeux de lancer le navigateur. Encore une fois, j'étais enthousiaste à l'idée de créer des outils de ligne de commande. Quelque chose comme ça.
git
TAB
REPL
où la coque se lève [^ 1]Cette fois, je vais définir une commande pour qu'elle puisse être enregistrée avec TAB
. Je n'ai pas pu trouver un bon échantillon, alors je l'ai créé lors de mon enquête [^ 2]
[^ 1]: REPL (Read-Eval-Print Loop) est une boucle d'entrée / d'évaluation / de sortie. Un interpréteur qui permet à l'utilisateur et à l'interpréteur d'exécuter de manière interactive des morceaux de code. Personnellement, je l'appelle un gars Cisco-ish.
[^ 2]: Il y a longtemps, j'ai écrit un article comme Autocomplete with my own command line tool, mais c'est la version ruby
. Comme une légère différence par rapport à la dernière fois, cette fois, c'est comme REPL
[^ 1]
Cette fois jusqu'à ce que je fasse quelque chose comme ça
Le modèle est fondamentalement comme celui-ci en utilisant Readline
. ① est appelé par TAB, et ② tourne par retour
Commande à un niveau
require "readline"
#Commandes candidates d'achèvement
pet_store = [ "pet", "store", "user"]
#① Appelé pour appuyer sur TAB et essayer de terminer avec select
Readline.completion_proc = proc do |input|
pet_store.select { |name| name.start_with?(input) }
end
#② Appuyez sur retour pour tourner
while input = Readline.readline("$ ", false)
if input
break if input == "q"
p Readline.line_buffer.split if !input.empty?
end
end
Dans le cas d'une couche, c'est facile car les candidats de commande sont fixes
Pour une commande multicouche, je pense que la commande du candidat à l'achèvement dans l'exemple précédent doit être modifiée en fonction du contexte actuel (commande précédemment sélectionnée).
Le fait est que le "prochain candidat" doit être remplacé dynamiquement selon la "commande actuellement sélectionnée". Prédéfinissons donc quelque chose comme un arbre de commandes. Puisqu'il s'agit d'une structure arborescente, essayez d'utiliser 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
#Basculer dynamiquement "prochain candidat" en fonction de la "commande actuellement sélectionnée" (descendre dans l'arborescence)
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
Qu'est-ce que je fais:
--Définissez la hiérarchie des commandes avec YAML
--L'entrée du clavier a été reçue par Readline
à ʻinput`.
--Lorsque «TAB» est atteint, le traitement de «proc» fonctionne, et la séquence actuelle des candidats de commande est remplacée (dans la hiérarchie) en fonction de «l'entrée» à ce moment-là.
select
pour ce tableauC'est comme
Cela peut être un cas mineur, mais avec la méthode ci-dessus, le bon candidat est affiché lorsque plusieurs candidats correspondent encore même si les candidats de commande se terminent jusqu'à "pet" tels que "pet" et "petGroup". ne pas. Cela veut trouver un candidat pour pet
ou petGroup
en complétant jusqu'à pet
, mais il correspond à la clé Hash pet
et descend d'un niveau, donc sous pet
C'est parce que la hiérarchie devient un candidat. Pour éviter cela, j'ai essayé ce qui suit 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
Vous avez maintenant le comportement attendu et vous pouvez taper confortablement.
Maintenant que cela fonctionne, passons à l'analyse et au traitement des commandes. S'il existe une meilleure méthode de mise en œuvre pour la réalisation, donnez-moi quelques conseils!