Question [fonction] Parsing conditionnelle

Plus d'informations
il y a 12 ans 4 semaines #12913 par Laurent Dardenne
Nouvelle version en fin de Thread.

Voici une fonction permettant de simuler basiquement des directives de parsing conditionnelle,
le principe est de délimiter une ou plusieurs lignes de code par un mot clé de début de directive et un mot clé de fin de directive :
[code:1]
#<DEFINE %DEBUG%>
#code utilisé lors du développement
Write-Debug \"$TempFile\"
#<UNDEF %DEBUG%>
[/code:1]
Les lignes de code entre la directive #<DEFINE %NomDeLaDirective%> et #<UNDEF %NomDeLaDirective%> pourront être supprimées lors d'une livraison en production.

Ceci permettra également de coder dans un seul fichier des traitements spécifiques à une version de Powershell :
[code:1]
#Enumére une collection
#<DEFINE %V2%>
dir | % { $_.FullName }
#<UNDEF %V2%>

#<DEFINE %V3%>
(dir).FullName
#<UNDEF %V3%>
[/code:1]
Cette fonction permet également de placer une indication de suppression d'une seule ligne :
[code:1]
#Attribut de test fonctionnel, le code de l'attribut n'est pas livrée en production #<%REMOVE%>
[FunctionalType('FilePath')] #<%REMOVE%>
[/code:1]
Cette fonction n'est pas un préprocessor, mais juste un outil de suppression de lignes.
[code:1]
#Remove-Conditionnal.ps1
Function Remove-Conditionnal {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true,ValueFromPipeline = $true)]
$InputObject,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$true,position=0,ParameterSetName=\"Keyword\")]
[String[]]$ConditionnalsKeyWord,
[Parameter(ParameterSetName=\"Clean\")]
[Switch] $Clean
)
Begin {
function New-ParsingDirective {
param(
[Parameter(Mandatory=$true,position=0)]
$Name,
[Parameter(Mandatory=$true,position=1)]
$Line
)
#Les paramétres liés définissent aussi les propriétés de l'objet
$O=New-Object PSObject -Property $PSBoundParameters
$O.PsObject.TypeNames[0] = \"ParsingDirective\"
$O|Add-Member ScriptMethod ToString {'{0}:{1}' -F $this.Name,$this.Line} -force -pass
}#New-ParsingDirective

$oldofs,$ofs=$ofs,'|'
if ( $Clean)
{$RegexDefine=\"^\s*#<\s*DEFINE\s*%(?<ConditionalKeyWord>.*[^%\s])%\"}
else
{
$ConditionnalsKeyWord|
Where {$Directive=$_; $Directive.Contains(' ')}|
Foreach {Throw \"Une directive contient des espaces :$Directive\" }
$RegexDefine=\"^\s*#<\s*DEFINE\s*%(?<ConditionalKeyWord>$ConditionnalsKeyWord)%\"
}
$Directives=New-Object System.Collections.Queue
$ofs=$oldofs
}

Process {
$LineNumber=0;
$isDirectiveBloc=$False

#renvoi toutes les lignes sauf celles du bloc délimitées par une 'directive'
if ($Clean)
{$CurrentDirective='.*[^%\s]'}
else
{$CurrentDirective=$null}


$Result=$InputObject|
Foreach-Object {
$LineNumber++
[string]$Line=$_
Write-Debug \"`t Traite $Line `t isDirectiveBloc=$isDirectiveBloc\"
switch -regex ($_)
{
#Recherche le mot clé de début d'une directive, puis l'empile
$RegexDefine { Write-Debug \"Match DEFINE\"
if (-not $Clean)
{
$CurrentDirective=$Matches.ConditionalKeyWord
Write-Debug \"Enqueue $CurrentDirective\"
$O=New-ParsingDirective $CurrentDirective $LineNumber
$Directives.Enqueue($O)
$isDirectiveBloc=$True
}
continue
}

#Recherche le mot clé de fin de la directive courante, puis dépile
\"^\s*#<\s*UNDEF %${CurrentDirective}%>\" {
Write-Debug \"Match UNDEF\"
if (-not $Clean)
{
if ($Directives.Count -gt 0)
{
Write-Debug \"Dequeue $CurrentDirective\"
$CurrentDirective=($Directives.Dequeue()).Name
if ($Directives.Count -gt 0) {Write-Debug \"Reprend sur $CurrentDirective\"}
}
if ($Directives.Count -eq 0)
{Write-Debug \"Fin d'imbrication\"}
$isDirectiveBloc=$False;
}
continue
}
#Supprime la ligne
\"#<%REMOVE%>\" { Write-Debug \"Match REMOVE\"
if ($Clean)
{$Line -replace \"#<%REMOVE%>\",''}
continue
}
# \"^\s*#<%EXPAND \$(?<VarName>.*)%%>\" { #todo #<%REMOVE%>
# \"^\s*#<%INCLUDE \$(?<FileName>.*)%%>\" { #todo #<%REMOVE%>

default {
Write-Debug \"Match Default\"
#On traite les lignes qui ne se trouvent pas dans le bloc de la 'directive'
if ($isDirectiveBloc -eq $false)
{$Line}
}#default
}#Switch
}#Foreach
if ($Directives.Count -gt 0)
{
$oldofs,$ofs=$ofs,','
Write-Error \"Parsing annulé. Les directives suivantes n'ont pas de mot clé de fin : $Directives\"
$ofs=$oldofs
}
else
{ ,$Result } #renvoi un tableau
$Directives.Clear()
}#process
} #Remove-Conditionnal
[/code:1]
Quelques exemples.
Supprime du code utilisé pour le debug :
[code:1]
$Code=@'
Filter Test {
param ( [String]$ConditionnalsKeyWord )

Write-Host \"Code de la fonction\"
#<DEFINE %DEBUG%>
Write-Debug \"$TempFile\"
Write-Debug \"$FullPath\"
#<UNDEF %DEBUG%>
} #test
'@
Remove-Conditionnal -Input ($code -split \"`n\"«») -ConditionnalsKeyWord \"DEBUG\"
[/code:1]
Supprime du code utilisé pour le debug et pour des tests. L'imbrication de directives est possible :
[code:1]
$Code=@'
Filter Test {
param (
[String]$ConditionnalsKeyWord
)
Write-Host \"Code de la fonction\"

#<DEFINE %DEBUG%>
Write-Debug \"$TempFile\"
Write-Debug \"$FullPath\"
#<UNDEF %DEBUG%>

#Imbrication de directive
#<DEFINE %TEST%>
Set-Location C:\Temp
#<DEFINE %DEBUG%>
Write-Debug \"Fin\"
#<UNDEF %DEBUG%>
\"Remove-Conditionnal.ps1\"|Remove-Conditionnal \"TEST\"
#<UNDEF %TEST%>
} #test
'@

#Ne supprime que le code de la directive Debug,
# le code de la directive Test n'est pas supprimé
Remove-Conditionnal -Input ($code -split \"`n\"«») -ConditionnalsKeyWord \"DEBUG\"

#Ne supprime que le code de la directive Test,
# le code de la directive Debug qui n'est pas imbriqué dans une directive Test
# n'est pas supprimé
Remove-Conditionnal -Input ($code -split \"`n\"«») -ConditionnalsKeyWord \"Test\"

#Supprime le code de la directive Debug et celui de la directive Test.
Remove-Conditionnal -Input ($code -split \"`n\"«») -ConditionnalsKeyWord \"DEBUG\",\"Test\"
[/code:1]
Un dernier exemple permettant de générer à partir d'un seul fichier une fonction pour une version spécifique de Powershell :
[code:1]
$Code=@'
#<DEFINE %V3%>
#Requires -Version 3.0
#<UNDEF %V3%>
#<DEFINE %V2%>
#Requires -Version 2.0
#<UNDEF %V2%>

Filter Test {
param (
[FunctionalType('FilePath')] #<%REMOVE%>
[String]$ConditionnalsKeyWord
)
Write-Host \"Code de la fonction\"
#<DEFINE %V2%>
dir | % { $_.FullName }
#<UNDEF %V2%>

#<DEFINE %V3%>
(dir).FullName
#<UNDEF %V3%>

} #test
'@

#On supprime le code spécifique à la v3, le code est compatible avec la v2 uniquement
Remove-Conditionnal -Input ($code -split \"`n\"«») -ConditionnalsKeyWord \"V3\"

#On supprime le code spécifique à la v2, le code est compatible avec la v3 uniquement
Remove-Conditionnal -Input ($code -split \"`n\"«») -ConditionnalsKeyWord \"V2\"
[/code:1]
Cette fonction est en test.

Message édité par: Laurent Dardenne, à: 14/10/12 16:45

Message édité par: Laurent Dardenne, à: 16/10/12 17:31

Message édité par: Laurent Dardenne, à: 17/10/12 17:33

Message édité par: Laurent Dardenne, à: 18/10/12 14:52<br><br>Message édité par: Laurent Dardenne, à: 28/01/14 19:18

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 12 ans 3 semaines #12920 par Laurent Dardenne
Une nouvelle version qui implémente une action de nettoyage des directives restantes dans le fichier cible (-Clean).
Pour
[code:1]
$Code=@'
#&lt;DEFINE %V3%&gt;
#Requires -Version 3.0
#&lt;UNDEF %V3%&gt;
#&lt;DEFINE %V2%&gt;
#Requires -Version 2.0
#&lt;UNDEF %V2%&gt;

Filter Test {
param (
[FunctionalType('FilePath')] #&lt;%REMOVE%&gt;
[String]$ConditionnalsKeyWord
)
Write-Host \&quot;Code de la fonction\&quot;
#&lt;DEFINE %V2%&gt;
dir | % { $_.FullName } #v2
#&lt;UNDEF %V2%&gt;

#&lt;DEFINE %V3%&gt;
(dir).FullName #v3
#&lt;UNDEF %V3%&gt;

} #test
'@

#On supprime le code spécifique à la v3, le code est compatible avec la v2 uniquement
Remove-Conditionnal -Input ($code -split \&quot;`n\&quot;«») -ConditionnalsKeyWord \&quot;V3\&quot;|Remove-Conditionnal -Clean
[/code:1]
On obtient :
[code:1]
#Requires -Version 2.0
Filter Test {
param (
[FunctionalType('FilePath')] #&lt;%REMOVE%&gt;
[String]$ConditionnalsKeyWord
)
Write-Host \&quot;Code de la fonction\&quot;
dir | % { $_.FullName } #v2
} #test
[/code:1]
Qq erreurs d'imbrication sont désormais gérées. Le message d'erreur contient le nom et le numéro de la ligne concernée :
[code:1]
$Code=@'
Filter Test {
param (
[String]$ConditionnalsKeyWord
)
Write-Host \&quot;Code de la fonction\&quot;
#&lt;DEFINE %V2%&gt;
dir | % { $_.FullName }
#&lt;UN DEF %V2%&gt;

} #test
'@

Remove-Conditionnal -Input ($code -split \&quot;`n\&quot;«») -ConditionnalsKeyWord \&quot;V2\&quot;|Remove-Conditionnal Clean
#Remove-Conditionnal : Parsing annulé. Les directives suivantes n'ont pas de mot clé de fin : V2:6
[/code:1]

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 12 ans 3 semaines #12925 par Laurent Dardenne
Correction du code de la directive REMOVE.
Un exemple a exécuter avant une livraison :
[code:1]
function Pause ($Message=\&quot;Pressez une touche pour continuer...\&quot;«»)
{
Write-Host -NoNewLine $Message
$null = $Host.UI.RawUI.ReadKey(\&quot;NoEcho,IncludeKeyDown\&quot;«»)
Write-Host \&quot;\&quot;
}


$PathSource='C:\temp'
$VerbosePreference='Continue'
$Livraison='C:\Temp\Livraison'
Del \&quot;C:\Temp\Livraison\*.ps1\&quot; -ea 'SilentlyContinue'

$Directives=@('Debug','V3','Remove')
Dir \&quot;$PathSource\Test*.ps1\&quot;|
Foreach {Write-Verbose \&quot;Parse :$($_.FullName)\&quot;; $CurrentFileName=$_.Name;$_}|
Get-Content -ReadCount 0|
Remove-Conditionnal -ConditionnalsKeyWord $Directives|
Remove-Conditionnal -Clean|
Set-Content -Path {(Join-Path $Livraison $CurrentFileName)} -Force


Dir Test*.ps1|% {pause;cls; Write-Verbose \&quot;Pour $Directives | $($_.FullName)\&quot;;Type $_;Write-host ('-' *40);type \&quot;$Livraison\$($_.name)\&quot;}

[/code:1]

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 12 ans 3 semaines #12935 par Laurent Dardenne
Correction du code, le directive ne doit pas contenir d'espace.

Autre exemple d'usage, on peut générer du code spécifique à une version de Windows :
[code:1]$code=@'
Filter Test {
#&lt;DEFINE %SEVEN%&gt;
#http://psclientmanager.codeplex.com/ #<%REMOVE%>
Import-Module PSClientManager #Seven
Add-ClientFeature -Name TelnetServer
#&lt;UNDEF %SEVEN%&gt;

#&lt;DEFINE %2008R2%&gt;
Import-Module ServerManager #2008R2
Add-WindowsFeature -Name Telnet-Server
#&lt;UNDEF %2008R2%&gt;
}
'@

#Le code est compatible avec Windows 2008R2 uniquement
,($code -split \&quot;`n\&quot;«»)|
Remove-Conditionnal -ConditionnalsKeyWord \&quot;SEVEN\&quot;,'REMOVE'|
Remove-Conditionnal -Clean[/code:1]<br><br>Message édité par: Laurent Dardenne, à: 18/10/12 14:57

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 10 ans 9 mois #16778 par Laurent Dardenne
Une nouvelle version permettant l'inclusion de fichier.
[code:1]
@'
#Fichier d'inclusion C:\Temp\Test1.ps1
1-un
'@ &gt; C:\Temp\Test1.ps1

@'
#Fichier d'inclusion C:\Temp\Test2.ps1
#&lt;INCLUDE %'C:\Temp\Test3.ps1'%&gt;
2-un
#&lt;DEFINE %DEBUG%&gt;
2-deux
#&lt;UNDEF %DEBUG%&gt;
'@ &gt; C:\Temp\Test2.ps1

@'
#Fichier d'inclusion C:\Temp\Test3.ps1
3-un
#&lt;INCLUDE %'C:\Temp\Test1.ps1'%&gt;
$Logger.Debug('Test') #&lt;%REMOVE%&gt;
#&lt;DEFINE %PSV2%&gt;
3-deux
#&lt;UNDEF %PSV2%&gt;
'@ &gt; C:\Temp\Test3.ps1

Dir C:\Temp\Test2.ps1|
Get-Content -ReadCount 0|
Remove-Conditionnal -ConditionnalsKeyWord 'DEBUG'
[/code:1]
Ces instructions crées trois fichiers. L'appel à Remove-Conditionnal génère le code suivant :
le code suivant :

#Fichier d'inclusion C:\Temp\Test2.ps1
#Fichier d'inclusion C:\Temp\Test3.ps1
3-un
#Fichier d'inclusion C:\Temp\Test1.ps1
1-un
#&lt;DEFINE %PSV2%&gt;
3-deux
#&lt;UNDEF %PSV2%&gt;
2-un

La directive Remove est tjrs exécuté, pour éviter de supprimer les lignes ainsi marquées, exécuter la fonction auparavant avec -Clean.<br><br>Message édité par: Laurent Dardenne, à: 28/01/14 19:17

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 10 ans 9 mois #16854 par Laurent Dardenne
Une nouvelle version offrant une meilleure prise en charge des erreurs d'imbrication de directives et l'usage via des paramètres des directives Remove, UnComment et Include ce qui permet des transformations au cas par cas.
L'usage du paramètre -ConditionnalsKeyWord n'est plus obligatoire et l'ajout du paramètre -Container mémorise le nom de la source de données en cours d'analyse.<br><br>Message édité par: Laurent Dardenne, à: 5/02/14 13:54

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.123 secondes
Propulsé par Kunena