2016-03-14 31 views
5

Obecnie próbuję użyć funkcji AST wprowadzonej w PowerShell 3.0, aby zmodyfikować ScriptBlock. Moim wymaganiem jest, aby wszystkie parametry w bloku parametrów skryptu ScriptBlock uzyskały atrybut [Parameter(Mandatory)].Modyfikacja i zakres funkcji PowerShell AST

Zasadniczo kod powinien zmodyfikować to:

Param([string]$x) 

Write-Host $x 

do tego:

Param([Parameter(Mandatory)][string]$x) 

Write-Host $x 

Jednak wpadłem na problem podczas dodawania ten nowy atrybut, ponieważ oczekuje IScriptExtent i nie jestem na pewno powinienem stworzyć nowy IScriptExtent.

Jak mogę utworzyć nowy zakres skryptu? Jakie wartości mogę użyć dla pozycji? Czy muszę zmienić pozycję wszystkich poniższych zakresów?

Próbowałem ponownie wykorzystywać zakres każdego modyfikowanego przeze mnie parametru, ale niestety nie wydaje się, aby przyniósł on wyniki, które powinien (np. Gdy wywołuję ToString zmodyfikowanego ScriptBlock, nie widzę żadnych zmian).

Moja implementacja do tej pory oparta jest na ICustomAstVisitor znalezionych here.

Najważniejsza metoda wygląda następująco:

public object VisitParameter(ParameterAst parameterAst) 
{ 
    var newName = VisitElement(parameterAst.Name); 

    var extent = // What to do here? 

    var mandatoryArg = new AttributeAst(extent, new ReflectionTypeName(typeof (ParameterAttribute)), 
     new ExpressionAst[0], 
     new[] {new NamedAttributeArgumentAst(extent, "Mandatory", new ConstantExpressionAst(extent, true), true)}); 

    var newAttributes = new[] {mandatoryArg}.Concat(VisitElements(parameterAst.Attributes)); 
    var newDefaultValue = VisitElement(parameterAst.DefaultValue); 
     return new ParameterAst(parameterAst.Extent, newName, newAttributes, newDefaultValue); 
} 

Odpowiedz

3

nazwy, które zaczynają I są zazwyczaj interfejsy. Nie są to klasy, które tworzysz, są to kontrakty sortów, które określają, że dana klasa implementuje pewien znany zestaw funkcji. Na przykład [hashtable] implementuje IEnumerable. Oznacza to, że wszystko, co wie, jak pracować z interfejsem IEnumerable i działać na tej klasie; możesz stworzyć własną klasę, która implementuje interfejs, a kod, który nigdy nie mógł wiedzieć o twojej klasie lub co robi, może nadal wchodzić z nią w interakcję w sposób zdefiniowany przez IEnumerable (co w tym przypadku jest sposobem na iterację).

Tak więc, gdy funkcja deklaruje parametr z typem interfejsu, nie szuka żadnej konkretnej klasy, szuka jakiejkolwiek klasy, która implementuje ten interfejs.

Następnym krokiem jest sprawdzenie, które typy implementują ten interfejs. Oto niektóre kodu PowerShell kiedyś znaleźć te:

[System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() | Where-Object { 
    [System.Management.Automation.Language.IScriptExtent].IsAssignableFrom($_) 
} 

Z tego widzimy, co następuje:

IsPublic IsSerial Name          BaseType              
-------- -------- ----          --------              
True  False IScriptExtent                       
False False InternalScriptExtent      System.Object            
False False EmptyScriptExtent      System.Object            
True  False ScriptExtent        System.Object            

Pierwsze notowanie jest sam interfejs. Z pozostałych trzech, dwa z nich nie są publiczne, więc po prostu pozostawia ScriptExtent.

Możesz utworzyć jedną z nich za pomocą New-Object, ale musisz podać pozycje początkową i końcową jako obiekty [ScriptPosition]. Nie jestem do końca pewien, co to powinno być, nie widząc więcej kodu.

+0

wiem co interfejs jest moje pytanie kręci się wokół mnie nie do końca jest w stanie zorientować się, jak zakresy pracy w PowerShell podczas tworzenia nowego kodu (istnieje wiele przykładów ludzi modyfikowania kodu i ponowne zasięgi, ale nie można znaleźć żadnego przykładu, w którym ludzie stworzyli nowy kod). – chrischu

+1

@chrischu nie jest całkiem jasne z twojego pytania, że ​​znasz interfejsy, ponieważ pytasz, jak stworzyć nowy "IScriptExtent", więc uznałem, że najlepiej jest błądzić po bezpiecznej stronie i wyjaśnić, zwłaszcza że może to być pomocne inni użytkownicy, którzy znajdą twoje pytanie, ale nie wiesz, co to jest interfejs. Możesz również rozważyć umieszczenie w swoim pytaniu tego, co dotychczas wypróbowałeś. – briantist

3

Zakres skrypt służy przede wszystkim do raportowania błędów, ale jest również wykorzystywany do debugowania (. Na przykład ustawienie punktu przerwania linii)

Generalnie opcje syntetyzowanych skryptu (jak swoim przykładzie) są:

  • ponowne wykorzystanie istniejącej ast, przypuszczalnie w pobliżu/związane z ast jesteś dodając
  • użyć pustego ast (zasadniczo tworzyć instancje ScriptExtent i ScriptPosition bez pliku, pusta linia)
  • stworzyć syntetyczny w jakim stopniu pomaga to w debugowaniu, może z pewnymi wyjątkowymi treściami:

W twoim przykładzie odpowiednie są dowolne z powyższych. Druga opcja jest najprostsza. Trzecia opcja to tylko wariant drugiego, ale możesz ustawić treść na coś użytecznego, np.

<#Generated: [Parameter(Mandatory)] #> 
+0

Interesujące. Chociaż mam jeden problem, to wywołanie ToString() na zmodyfikowanym AST zwraca stary kod i moim zdaniem było tak ze względu na stary zakres. Czy istnieje inny dobry sposób, aby włączyć AST z powrotem do kodu źródłowego, który nie opiera się na poprawnych rozmiarach? – chrischu

+0

Nie tak łatwo - ładna drukarka jest całkowicie rozsądną rzeczą do wdrożenia w oparciu o Ast, ale nie jestem tego świadomy, poza jakimś przykładowym kodem, który napisałem dawno temu, który tak naprawdę nie wykonuje bardzo dobrej pracy formatowanie. –

+2

Cóż, "ładna drukarka" to cały problem: nie potrzebuję nawet kodu, żeby był ładny, wystarczy, że otrzymam kod po zmodyfikowaniu AST, a to nie działa bez podania "prawidłowych" rozmiarów. W pewnym sensie wydaje się nawet, że łatwiej byłoby po prostu użyć modyfikacji ciągów, aby zmodyfikować kod zamiast dubbingu z AST, co byłoby dość wstydem. – chrischu