PowerShell podpowiada komendy gita
Obiecywałem, że spróbuję i spróbowałem. I choć tak jak sądziłem, bez kompilowania się nie obyło, to samo tworzenia prościutkiego modułu, który zacznie podpowiadać polecenia okazało się stosunkowo proste. Na pierwszy rzut – prosty modulik, który podpowiada polecenia gita. Nie poprzestaniemy jednak jedynie na tym – pozwolimy pomocy pomagać nam „bardziej”.
Zacznijmy jednak od początku. o PowerShell Predictors pisałem w poprzednim artykule. W Windows PowerShell możemy skorzystać tylko z jednego rodzaju podpowiedzi – opartych o naszą historię. Jeśli jednak możemy korzystać z nowszej wersji PowerShella (konkretniej: nowszej niż 7.2) i odpowiednio “świeżej” wersji modułu PSReadLine (minimum to 2.2.2), to do dyspozycji mamy możliwość tworzenia specjalnych wtyczek, które podpowiedzi rozszerzą. W czasie PowerShell Conference Europe jeden z członków zespołu, Steven Bucher, zademonstrował nie tylko jak działają istniejące “wtyczki”, ale podał linkę do dokumentacji opisującej jak takie wtyczki tworzyć.
Procedura jest stosunkowo prosta, choć bez znajomości (choćby podstawowej) C# się nie obędzie:
- instalujemy wymaganą wersję platformy dotnet
- tworzymy nowy projekt korzystając z polecenia dotnet new classlib –name NaszaNazwa
- modyfikujemy plik projektu dodając w nim odnośnik do SDK PowerShella
- zmieniamy nazwę pliku i wklejamy do niego przykładowy kod
- kompilujemy za pomocą dotnet build
Utworzoną DLL-kę po prostu wczytujemy jak każdy inny moduł, poleceniem Import-Module.
Oczywiście, rozwiązane przykładowe jest kompletnie bezużyteczne. Daje jednak wgląd w to, gdzie skupić musimy nasze wysiłki, by tworzyć podpowiedzi przydatne użytkownikom. Będzie to przede wszystkim metoda GetSuggestion.
Najpierw spróbowałem więc stworzyć sobie listę poleceń gita, które moim zdaniem przydają się na co dzień, a następnie generować podpowiedzi w oparciu o próby uruchomienia gita przez użytkownika:
public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContext context, CancellationToken cancellationToken) | |
{ | |
string input = context.InputAst.Extent.Text; | |
if (input.StartsWith("git")) | |
{ | |
List<PredictiveSuggestion> suggestions = new List<PredictiveSuggestion>{}; | |
List<string> gitCommands = new List<string>{ | |
"git commit -m 'your message'", | |
"git add .", | |
"git push", | |
"git checkout -b newBranch", | |
"git rebase -i", | |
"git log --oneline -n 3" | |
}; | |
foreach (string command in gitCommands) | |
{ | |
if (command.StartsWith(input)) | |
{ | |
suggestions.Add(new PredictiveSuggestion(command)); | |
} | |
} | |
if (suggestions.Count == 0) | |
{ | |
return default; | |
} | |
else | |
{ | |
return new SuggestionPackage(suggestions); | |
} | |
} | |
else | |
{ | |
return default; | |
} | |
} |
Wyszło całkiem nieźle, problem w tym, że to troszkę wąski zakres zastosowań. Następnie do głowy przyszedł mi inny pomysł: jeśli dłubiecie pracowicie jakiś moduł i dbacie o odpowiednią do niego pomoc, to pewnie macie masę przykładów korzystania z tworzonych przez was poleceń… Sam często zaczynam pracę z nowym poleceniem od wywołania pomocy z parametrem Examples. Czemu więc nie skorzystać z tego i nie zacząć generować podpowiedzi w oparciu o przykłady z pomocy?
Najpierw chciałem zaprząc do pracy polecenie Get-Help – ale paradoksalnie korzystanie z PowerShella w tej sytuacji nie jest zbyt proste. Dodatkowo w przypadku pomocy opartej o komentarze efekt nie jest najlepszy. Nieco lepiej wygląda sytuacja z pomocą w pliku XML (na przykład wygenerowaną przez moduł platyPS). Wtedy możemy po prostu “wyciągnąć” kod z przykładów za pomocą XPath:
// do konstruktora dodajemy _examples pobrane z pliku, by nie czytać ich za każdym razem... | |
internal HelpExamplePredictor(string guid) | |
{ | |
_guid = new Guid(guid); | |
string moduleFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | |
string ewsHelpPath = Path.Combine(moduleFolder, "EWS-help.xml"); | |
XmlDocument xmlDoc = new XmlDocument(); | |
xmlDoc.Load(ewsHelpPath); | |
XmlNamespaceManager manager = new XmlNamespaceManager(xmlDoc.NameTable); | |
manager.AddNamespace("dev", "http://schemas.microsoft.com/maml/dev/2004/10"); | |
_examples = new List<string>{}; | |
foreach (XmlNode node in xmlDoc.SelectNodes("*//dev:code", manager)) | |
{ | |
_examples.Add(node.InnerText.Replace(@"PS C:\> ", "")); | |
} | |
} | |
public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContext context, CancellationToken cancellationToken) | |
{ | |
if (context.TokenAtCursor.TokenFlags == TokenFlags.CommandName) | |
{ | |
string command = context.TokenAtCursor.Text; | |
List<PredictiveSuggestion> suggestions = new List<PredictiveSuggestion>{}; | |
foreach (string example in _examples) | |
{ | |
if (example.ToLower().StartsWith(command.ToLower())) | |
{ | |
suggestions.Add(new PredictiveSuggestion(example)); | |
} | |
} | |
if (suggestions.Count == 0) | |
{ | |
return default; | |
} | |
else | |
{ | |
return new SuggestionPackage(suggestions); | |
} | |
} | |
else | |
{ | |
return default; | |
} | |
} |
Finalny efekt jest niczego sobie, ale nadal pozostaje sporo pola do usprawnień. Dla przykładu: “podmianę” jedynie fragmentu zawierającego polecenie, by podpowiedzi działały również wtedy, gdy skorzystać z nich zechcemy w ramach rozbudowanej “rurki”. Myślę jednak, że już teraz wyniki są obiecujące:
Problem? Niestety, póki co większość czasu spędzam w Windows PowerShell, gdzie tego rodzaju sztuczki nie działają. Na szczęście ostatnio mam dobrą “wymówkę” by częściej używać wersję siódmą (VMware vSphere DSC), może więc i okazji do pracy z tego rodzaju wtyczkami będzie coraz więcej.