Get-OpenSCAD.ps1
function Get-OpenSCAD { <# .Synopsis Gets OpenSCAD designs .Description Gets information about OpenSCAD designs. .Example Get-OpenSCAD -ScadFilePath .\MyDesign.scad .Link Invoke-OpenSCAD #> param( # The path to an OpenSCAD file. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Fullname')] [string] $ScadFilePath ) begin { filter =>[OpenScad.Parameter] { $p = $_ $type,$defaultValue = @(if ($p.Groups["BooleanValue"].Success) { [switch], ($p.Groups["BooleanValue"].value -match $true) } elseif($p.Groups["NumberValue"].Success) { [double], ($p.Groups["NumberValue"].value -as [double]) } elseif ($p.Groups["StringValue"].Success) { [string], $p.Groups["StringValue"].Value } elseif ($p.Groups["ListValue"].Success) { [PSObject[]], $p.Groups["ListValue"].value -replace '\[', '@(' -replace '\]', ')' } else { [PSObject], $null }) if ($defaultValue -eq 'undef') { $defaultValue = $null } elseif ($p.Groups['ConstantValue'].Success) { $defaultValue = $null } [PSCustomObject][Ordered]@{ Name=$p.Groups["Name"].Value Type = $type DefaultValue=$defaultValue } } $openScadRegex = @{ OpenSCAD_Include = [Regex]::new(@' (?m) # Set Multiline mode. Then, ^include # match the literal 'include' \s+ # and the obligitory whitespace. \<(?<Include>[^\>]+)\> '@, 'IgnoreCase,IgnorePatternWhitespace', '00:00:05') OpenSCAD_Module = [Regex]::new(@' (?m) # Set Multiline mode. Then, ^module # match the literal 'module' \s+ # and the obligitory whitespace. (?<ModuleName>\w+) # Then match and extract the <ModuleName>. \s{0,} # Then, there may be whitespace. # The Module <ModuleParameters> are within () (?<Parameters> \( # An open parenthesis (?> # Followed by... [^\(\)]+| # any number of non-parenthesis character OR \((?<Depth>)| # an open parenthesis (in which case increment depth) OR \)(?<-Depth>) # a closed parenthesis (in which case decrement depth) )*(?(Depth)(?!)) # until depth is 0. \) # followed by a closing parenthesis )\s{0,} # Then, there may be whitespace. # The Module <ModuleDefinition> is Within {} (?<Definition> \{ # An open { (?> # Followed by... [^\{\}]+| # any number of non-bracket character OR \{(?<Depth>)| # an open curly bracket (in which case increment depth) OR \}(?<-Depth>) # a closed curly bracket (in which case decrement depth) )*?(?(Depth)(?!)) # until depth is 0. \} # followed by a } ) '@, 'IgnoreCase,IgnorePatternWhitespace', '00:00:05') OpenSCAD_Function = [Regex]::new(@' (?m) # Set Multiline mode. Then, (?<Comments>//[\s.]{0,}?$(?>\r\n|\n)){0,}^function # match the literal 'function' \s+ # and the obligitory whitespace. (?<Name>\w+) # Then match and extract the .Name \s{0,} # Then, there may be whitespace. # The .Parameters are within () (?<Parameters> \( # An open parenthesis (?> # Followed by... [^\(\)]+| # any number of non-parenthesis character OR \((?<Depth>)| # an open parenthesis (in which case increment depth) OR \)(?<-Depth>) # a closed parenthesis (in which case decrement depth) )*(?(Depth)(?!)) # until depth is 0. \) # followed by a closing parenthesis )\s{0,} # Then, there may be whitespace. \=\s{0,} # Then, there may be whitespace. (?:.|\s){0,}?(?=\z|;) '@, 'IgnoreCase,IgnorePatternWhitespace', '00:00:05') OpenSCAD_Customization = [Regex]::new(@' (?m)^(?<Name>\w+) # Optional Whitespace \s{0,} # Optional Whitespace \=\s{0,} # Optional Whitespace (?> (?<Value>(?<NumberValue>[\d\.]+) # A numeric value | (?<BooleanValue>true|false) # A boolean value | \"(?<StringValue>(?:.|\s)*?(?<!\\)") # A string value | (?<ListValue>(?<BalancedBrackets> \[ # An open bracket (?> # Followed by... [^\[\]]+| # any number of non-bracket character OR \[(?<Depth>)| # an open bracket (in which case increment depth) OR \](?<-Depth>) # a closed bracket (in which case decrement depth) )*(?(Depth)(?!)) # until depth is 0. \] # followed by a closing bracket ) ) # A List Value ))\s{0,} # Optional Whitespace \; # Semicolon (?<RestOfLine>.*$) '@, 'IgnoreCase,IgnorePatternWhitespace', '00:00:05') OpenSCAD_Use = [Regex]::new(@' (?m) # Set Multiline mode. Then, ^use # match the literal 'use' \s+ # and the obligitory whitespace. \<(?<Use>[^\>]+)\> '@, 'IgnoreCase,IgnorePatternWhitespace', '00:00:05') OpenSCAD_Parameter = [Regex]::new(@' (?<=[\(\,]) # After a ( or a , \s{0,} # Optional Whitespace (?<Name>\w+) # The Parameter Name \s{0,} # Optional Whitespace # A literal = is used to determine if it Has a default value (?<HasDefaultValue>=)? # If there is a default value (?(HasDefaultValue)(\s{0,} # Allow optional whitespace # Match the value, which could be (?> (?<Value>(?<ListValue>(?<BalancedBrackets> \[ # An open bracket (?> # Followed by... [^\[\]]+| # any number of non-bracket character OR \[(?<Depth>)| # an open bracket (in which case increment depth) OR \](?<-Depth>) # a closed bracket (in which case decrement depth) )*(?(Depth)(?!)) # until depth is 0. \] # followed by a closing bracket ) ) # A List Value | (?<NumberValue>[\d\.]+) # A number | (?<BooleanValue>true|false) # A boolean literal | (?<ConstantValue>\w+) # A constant value | \" # A string (?<StringValue>(?:.|\s)*?(?<!\\))" | (?<Expression>(?<BalancedParenthesis> \( # An open parenthesis (?> # Followed by... [^\(\)]+| # any number of non-parenthesis character OR \((?<Depth>)| # an open parenthesis (in which case increment depth) OR \)(?<-Depth>) # a closed parenthesis (in which case decrement depth) )*(?(Depth)(?!)) # until depth is 0. \) # followed by a closing parenthesis ) )))))\s{0,} '@, 'IgnoreCase,IgnorePatternWhitespace', '00:00:05') } } process { $resolvedScadFile = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($ScadFilePath) if (-not $resolvedScadFile) { return } $CustomizerFile = $resolvedScadFile -replace '\.scad$', '.json' $customizerPresets = if (Test-Path $CustomizerFile) { Get-content $CustomizerFile -Raw | ConvertFrom-Json | Where-Object ParameterSets | Select-Object -ExpandProperty ParameterSets } else {$null } $openScadContent = Get-Content -Raw -LiteralPath $resolvedScadFile $allMatches = @(foreach ($regex in $openScadRegex.GetEnumerator()) { if ($regex.Key -in 'OpenScad_Parameter') { continue } foreach ($m in $regex.Value.Matches($openScadContent)) { $m | Add-Member NoteProperty ExpressionName $regex.Key -Force -PassThru } }) | Sort-Object Index $uses = @() $includes = @() $modules = @() $functions = @() $customizations = @() foreach ($match in $allMatches) { switch ($match.ExpressionName) { OpenSCAD_Use { $uses += $match.Groups["Use"].Value } OpenSCAD_Include { $includes += $match.Groups["Include"].Value } OpenSCAD_Module { $commentMatchStart = $match.Index $parameters = $openScadRegex.OpenSCAD_Parameter.Matches(($match.Groups["Parameters"].Value)) $scadInfo = [PSCustomObject]@{ ModuleName = $match.Groups["ModuleName"].Value Parameters = @($parameters | =>[OpenSCAD.Parameter]) Definition = $match.Groups["Definition"] FileName = "$($resolvedScadFile | Split-Path -Leaf)" -replace '\.scad$' DirectoryName = "$($resolvedScadFile | Split-Path | Split-Path -Leaf)" Match = $match OpenScadPath = "$resolvedScadFile" } $modules += $scadInfo } OpenSCAD_Customization { $customizations += $match |=>[OpenSCAD.Parameter] } OpenSCAD_Function { $parameters = $openScadRegex.OpenSCAD_Parameter.Matches(($match.Groups["Parameters"].Value)) $scadInfo = [PSCustomObject]@{ ModuleName = $match.Groups["ModuleName"].Value Parameters = @($parameters | =>[OpenSCAD.Parameter]) Definition = $match.Groups["Definition"] FileName = "$($resolvedScadFile | Split-Path -Leaf)" -replace '\.scad$' DirectoryName = "$($resolvedScadFile | Split-Path | Split-Path -Leaf)" Match = $match OpenScadPath = "$resolvedScadFile" } $functions += $scadInfo } } } [PSCustomObject][Ordered]@{ ScadFilePath = "$resolvedScadFile" FileName = "$($resolvedScadFile | Split-Path -Leaf)" -replace '\.scad$' DirectoryName = "$($resolvedScadFile | Split-Path | Split-Path -Leaf)" Uses = $uses Includes = $includes Modules = $modules Functions = $functions Customizations = $customizations PresetNames = if ($customizerPresets) { $customizerPresets | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name } Presets = $customizerPresets PSTypeName = 'OpenScad.FileInfo' } } } |