codemelted.ps1
# ============================================================================= # [TERMINAL MODULE DEFINITION] ================================================ # ============================================================================= <#PSScriptInfo .AUTHOR mark.shaffer@codemelted.com .COPYRIGHT © 2024 Mark Shaffer. All Rights Reserved. MIT License .LICENSEURI https://github.com/CodeMelted/codemelted_developer/blob/main/LICENSE .PROJECTURI https://github.com/codemelted/codemelted_developer .ICONURI https://codemelted.com/assets/images/icon-codemelted-pwsh.png .EXTERNALMODULEDEPENDENCIES Microsoft.PowerShell.ConsoleGuiTools .TAGS pwsh pwsh-scripts pwsh-modules CodeMeltedDEV codemelted .GUID c757fe44-4ed5-46b0-8e24-9a9aaaad872c .VERSION 0.7.0 .RELEASENOTES 0.7.0 2025-03-09 - Completed --runtime testing on Linux. All three major systems available. - Clean up of script data definitions. - Corrected version number to reflect fully implemented use cases for the different use case groups. No full group is implemented yet. 0.5.5 2025-03-08 - Removed complaining item so Add-Type can complete properly. - This will make autocomplete in the future interesting. 0.5.4 2025-03-08 - Fixed missing type on Linux OS to allow proper running of the CLI. 0.5.3 2025-03-08 - Added --runtime and --monitor to the SDK use cases. 0.5.2 2025-03-03 - Added --network to the SDK use cases. - Only fetch action exists at this time. 0.5.1 2025-02-23 - Tailored the script to match the use case groups. - Implemented the --data-check, --json, and --string-parse options of the Data use cases, the --console of the User Interface use cases and the --logger of the SDK use cases. - The --disk use case is in development and exposed. 0.1.1 2025-01-08 - Broke out the --json into individual actions. - Updated the --help to reflect the module interface going forward. 0.1.0 2024-12-28 - Initial release of the codemelted CLI with the --json use case implemented. #> # ----------------------------------------------------------------------------- # [Main Parameter Definition] ------------------------------------------------- # ----------------------------------------------------------------------------- <# .DESCRIPTION A CLI to facilitate common developer use cases on Mac / Linux / Windows OS. #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [ValidateSet( # Module Definition Use Case "--version", "--help", # Async I/O Use Cases # Data Use Cases "--data-check", "--disk", "--json", "--string-parse", # NPU Use Cases # SDK Use Cases "--logger", "--network", "--runtime", # User Interface Use Cases "--console" )] [string] $Action, [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 1 )] [hashtable]$Params ) # ----------------------------------------------------------------------------- # [Data Types] ---------------------------------------------------------------- # ----------------------------------------------------------------------------- # .NET Assemblies Add-Type -AssemblyName Microsoft.PowerShell.Commands.Utility class CodeMeltedAPI { static [int] $logLevel = [CLogRecord]::offLogLevel static [scriptblock] $logHandler = $null } # A response object returned from the codemelted_network fetch action. # Contains the statusCode, statusText, and the data. class CFetchResponse { # Member Fields [int] $statusCode [string] $statusText [object] $data # Will treat the data as a series of bytes if it is that or return $null. [byte[]] asBytes() { return $this.data -is [byte[]] ? $this.data : $null } # Will treat the data as a JSON object if it is that or return $null. [hashtable] asObject() { return $this.data -is [hashtable] ? $this.data : $null } # Will treat the data as a string if it is that or return $null. [string] asString() { return $this.data -is [string] ? $this.data : $null } # Constructor for the class transforming the response into the appropriate # data for consumption. CFetchResponse($resp) { $this.statusCode = $resp.StatusCode $this.statusText = $resp.StatusDescription [string] $headers = $resp.Headers | Out-String if ($headers.ToLower().Contains("application/json")) { $this.data = codemelted_json @{ "action" = "parse"; "data" = $resp.Content } $this.data -is [hashtable] } else { $this.data = $resp.Content } } } # Provides the log record object to pass to a handler for post processing. # Attached to it is the module log level along with the current captured # log level, data, and the time it was logged. class CLogRecord { # Constants static [int] $debugLogLevel = 0 static [int] $infoLogLevel = 1 static [int] $warningLogLevel = 2 static [int] $errorLogLevel = 3 static [int] $offLogLevel = 4 # Utility to translate to the constants to string representation. static [string] logLevelString([int]$logLevel) { if ($logLevel -eq [CLogRecord]::debugLogLevel) { return "DEBUG" } elseif ($logLevel -eq [CLogRecord]::infoLogLevel) { return "INFO" } elseif ($logLevel -eq [CLogRecord]::warningLogLevel) { return "WARNING" } elseif ($logLevel -eq [CLogRecord]::errorLogLevel) { return "ERROR" } elseif ($logLevel -eq [CLogRecord]::offLogLevel) { return "OFF" } return "UNKNOWN" } # Utility to translate from string to constant log level number. static [int] logLevelInt([string]$logLevel) { if ($logLevel.ToLower() -eq "debug") { return [CLogRecord]::debugLogLevel } elseif ($logLevel.ToLower() -eq "info") { return [CLogRecord]::infoLogLevel } elseif ($logLevel.ToLower() -eq "warning") { return [CLogRecord]::warningLogLevel } elseif ($logLevel.ToLower() -eq "error") { return [CLogRecord]::errorLogLevel } return -1 } # Member Fields [string]$timestamp [int]$moduleLogLevel = -1 [int]$logLevel = -1 [string]$data = "" # Constructor for the class. CLogRecord([int]$moduleLogLevel, [int] $logLevel, [string] $data) { $this.timestamp = (Get-Date -Format "yyyy/MM/dd HH:mm:ss.fff") $this.moduleLogLevel = $moduleLogLevel $this.logLevel = $logLevel $this.data = $data } [string] ToString() { return $this.timestamp + " [" + [CLogRecord]::logLevelString($this.logLevel) + "]: " + $this.data } } # ----------------------------------------------------------------------------- # [CLI Help System] ----------------------------------------------------------- # ----------------------------------------------------------------------------- function codemelted_help { <# .SYNOPSIS The codemelted Command Line Interface (CLI) Terminal Module. It allows a developer to execute the CodeMelted DEV | Module use cases within a pwsh terminal shell. This allows for building CLI tools, Terminal User Interface (TUI) tools, or building DevOps toolchain automation. SYNTAX: codemelted [Action] [Params] PARAMETERS: [Action] # To Learn About the CLI use cases. --help : Execute 'codemelted --help @{ "action" = "--use-case" }' to learn more about the CLI Actions. --version : Get current information about the codemelted CLI # Async I/O Use Cases TBD # Data Use Cases --data-check --disk (IN DEVELOPMENT. DON'T USE) --json --string-parse # NPU Use Cases TBD # SDK Use Cases --logger --monitor --network (IN DEVELOPMENT. fetch usable) --runtime # User Interface Use Cases --console [Params] The optional set of named arguments wrapped within a [hashtable] RETURNS: Will vary depending on the called [Action]. .LINK CodeMelted DEV | pwsh Module: https://codemelted.com/developer/assets/pwsh GitHub Source: https://github.com/CodeMelted/codemelted_developer/tree/main/terminal #> param( [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) [hashtable] $helpLookup = @{ # Async I/O Use Cases # Data Use Cases "--data-check" = { Get-Help codemelted_data_check }; "--disk" = { Get-Help codemelted_disk }; "--json" = { Get-Help codemelted_json }; "--string-parse" = { Get-Help codemelted_string_parse }; # NPU Use Cases # SDK Use Cases "--logger" = { Get-Help codemelted_logger }; "--network" = { Get-Help codemelted_network }; "--runtime" = { Get-Help codemelted_runtime }; # User Interface Use Cases "--console" = { Get-Help codemelted_console }; } if ($null -ne $Params) { if (-not $Params.ContainsKey("action")) { throw "--help expects action key to be specified" } elseif ($null -eq $helpLookup[$Params["action"]]) { throw "--help action specified did not find a help specification" } Invoke-Command -ScriptBlock $helpLookup[$Params["action"]] } else { Get-Help codemelted_help } } # ============================================================================= # [USE CASE DEFINITIONS] ====================================================== # ============================================================================= # ----------------------------------------------------------------------------- # [Async I/O Use Cases] ------------------------------------------------------- # ----------------------------------------------------------------------------- # TBD # ----------------------------------------------------------------------------- # [Data Use Cases] ------------------------------------------------------------ # ----------------------------------------------------------------------------- # TBD - Database function codemelted_data_check { <# .SYNOPSIS Provides basic data validation and type checking of dynamic variables within a powershell script. SYNTAX: # Checks that the specified 'data' [hashtable] has the specified 'key'. $answer = codemelted --data-check @{ "action" = "has_property"; # required "data" = [hashtable]; # required "key" = "name of key in data"; # required "should_throw" = $false # optional } # Checks that the specified 'data' variable is the expected typename # specified by the 'key'. $answer = codemelted --data-check @{ "action" = "type"; # required "data" = $variable; # required "key" = "typename"; # required (.NET type names) "should_throw" = $false # optional } # Checks that the specified 'data' is a well formed URI object. $answer = codemelted --data-check @{ "action" = "url"; # required "data" = "url string"; # required "should_throw" = $false # optional } RETURNS: [boolean] $true if data check passes, $false otherwise THROWS: [string] if 'should_throw' is set to $true in the $Params. #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) $action = $Params["action"] $data = $Params["data"] $key = $Params["key"] $shouldThrow = $Params["should_throw"] -is [boolean] ` ? $Params["should_throw"] : $false if ($null -eq $data) { throw "SyntaxError: codemelted --data-check Params did not have a " + "specified data key and value." } $answer = $false $throwMessage = "" if ($action -eq "has_property") { if (-not ($data -is [hashtable])) { throw "SyntaxError: codemelted --data-check Params data key was not " + "a [hashtable] value for the 'has_property' action." } elseif ([string]::IsNullOrEmpty($key) ` -or [string]::IsNullOrWhiteSpace($key)) { throw "SyntaxError: codemelted --data-check Params 'key' key " + "was not set." } $throwMessage = "$key did not exist for the codemelted --data-check " + "'has_property' action." $answer = $data.ContainsKey($key) } elseif ($action -eq "type") { if ([string]::IsNullOrEmpty($key) -or [string]::IsNullOrWhiteSpace($key)) { throw "SyntaxError: codemelted --data-check Params 'key' key " "was not set." } $throwMessage = "$key was not the expected type for the codemelted " + "--data-check 'type' action." $answer = $key.ToString().ToLower() -eq $data.GetType().Name.ToLower() } elseif ($action -eq "url") { $throwMessage = "$data failed the codemelted --data-check 'url' action." $answer = [uri]::IsWellFormedUriString($data.ToString(), 0) } else { throw "SyntaxError: codemelted --data-check Params did not have a " + "supported action key specified. Valid actions are has_property / " + "type / url" } # Handle how we are returning from this function whether to throw or return # boolean. if ($shouldThrow) { if ( -not $answer) { throw $throwMessage } else { return [void] $answer } } return $answer } function codemelted_disk { <# .SYNOPSIS Provides the actions to manage files and directories on disk. This includes getting a listing, determining if an item exists, what type it is along with being able to copy, delete, and moving items. Lastly you can determine the size of an item in bytes on disk. SYNTAX: # Copy or move a file / directory from the identified "src" location to # the specified "dest" location. The "mv" can also be used as a rename. $success = codemelted --disk @{ "action" = "cp" / "mv"; # required "src" = [string]; # required "dest" = [string]; # required "report" = $true # optional - warning if command fails } # Create a "src" directory or delete a "src" file / directory $success = codemelted --disk @{ "action" = "mkdir" / "rm"; # required "src" = [string]; # required "report" = $false # optional - warning if command fails } # Determine if a "src" file / directory exists or if it is of a # given type. $exists = codemelted --disk @{ "action" = "exists"; # required "src" = [string]; # required "type" = "file" / "directory"; # optional that type vs. just existing "report" = $false # optional - warning if command fails } # Get a listing of "src" directory for only the specified "type". This # means either only the directories, files, or recurse to get all # files and directories from the specified "src". $listing = codemelted --disk @{ "action" = "ls"; # required "src" = [string]; # required "type" = "directory" / "file" / "recurse"; # optional "report" = $false # optional } # Gets the size in bytes of a "src" file / directory. $sizeInBytes = codemelted --disk @{ "action" = "size"; # required "src" = [string]; # required "report" = $false # optional } RETURNS: [boolean] $true for successful actions of 'cp' / 'exists' / 'mkdir' / 'mv' / 'rm'. $false otherwise. [System.IO.DirectoryInfo] When executing an 'ls' action. [int] When executing a 'size' action. #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) $action = $Params["action"] $src = $Params["src"] $dest = $Params["dest"] $type = $Params["type"] $report = $Params["report"] ?? $false if ([string]::IsNullOrEmpty($src) ` -or [string]::IsNullOrWhiteSpace($src)) { throw "SyntaxError: codemelted --disk Params requires a src key / value" } elseif (-not ($report -is [boolean])) { throw "SyntaxError: codemelted --disk Params optional report key is " + "expected to be a [boolean] value" } try { if ($action -eq "cp") { if ([string]::IsNullOrEmpty($dest) ` -or [string]::IsNullOrWhiteSpace($dest)) { throw "SyntaxError: codemelted --disk Params dest key is expected." } $isSrcADirectory = (Test-Path $src -PathType Container) if ($isSrcADirectory) { Copy-Item -Path $src -Destination $dest -Recurse -Force ` -ErrorAction Stop return $true } Copy-Item -Path $src -Destination $dest -Force -ErrorAction Stop return $true } elseif ($action -eq "exists") { $answer = $type -eq "directory" ` ? (Test-Path $src -PathType Container) : $type -eq "file" ` ? (Test-Path $src -PathType Leaf) : (Test-Path $src) return $answer } elseif ($action -eq "ls") { $answer = $type -eq "directory" ` ? (Get-ChildItem -Path $src -Directory -ErrorAction Stop) : $type -eq "file" ` ? (Get-ChildItem -Path $src -File -ErrorAction Stop) : $type -eq "recurse" ` ? (Get-ChildItem -Path $src -Recurse -ErrorAction Stop) : (Get-ChildItem -Path $src -ErrorAction Stop) return $answer } elseif ($action -eq "mkdir") { New-Item -ItemType Directory -Path $src -Force -ErrorAction Stop return $true } elseif ($action -eq "mv") { if ([string]::IsNullOrEmpty($dest) ` -or [string]::IsNullOrWhiteSpace($dest)) { throw "SyntaxError: codemelted --disk Params dest key is expected." } Move-Item -Path $src -Destination $dest -Force -ErrorAction Stop return $true } elseif ($action -eq "rm") { Remove-Item -Path $src -Recurse -Force -ErrorAction Stop return $true } elseif ($action -eq "size") { $isFile = Test-Path $src -PathType Leaf if ($isFile) { $sizeInBytes = (Get-ChildItem $src -ErrorAction Stop).Length return $sizeInBytes } $sizeInBytes = (Get-ChildItem -Path $directoryPath -Recurse | ` Measure-Object -Property Length -Sum -ErrorAction Stop).Sum return $sizeInBytes } else { throw "SyntaxError: codemelted --disk Params did not have a " + "supported action key specified. Valid actions are cp / exists / ls " + "mkdir / mv / rm / size" } } catch [string] { throw } catch { if ($report) { Write-Warning $_.Exception.Message } return $false } } # TBD - File function codemelted_json { <# .SYNOPSIS Provides the facilities to create / copy JSON compliant .NET objects and the ability to parse / stringify that data for storage, transmission, and processing of the data. SYNTAX: # Create / copy data into a new [arraylist] $data = codemelted --json @{ "action" = "create_array"; # required "data" = $arrayListData # optional [arraylist] to copy } # Create / copy data into a new [arraylist] $data = codemelted --json @{ "action" = "create_object"; # required "data" = $hashTableData # optional [arraylist] to copy } # Parse a string into a [hashtable] or [arraylist] or generic datatype $data = codemelted --json @{ "action" = "parse"; # required "data" = $stringData # required [string] data } # Transform a [arraylist] or [hashtable] into string data $data = codemelted --json @{ "action" = "stringify"; # required "data" = $theData # required [arraylist] or [hashtable] data } RETURNS: [arraylist] for 'create_array' / 'parse' actions. [hashtable] for 'create_object' / 'parse' actions. [string] for 'stringify' action. $null for invalid data types that can't be translated. #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) $action = $Params["action"] $data = $Params["data"] try { if ($action -eq "create_array") { $obj = New-Object System.Collections.ArrayList if ($data -is [System.Collections.ArrayList]) { $obj = $data.Clone() } elseif ($data -is [array]) { $obj += $data } return $obj } elseif ($action -eq "create_object") { $obj = $data -is [hashtable] ? $data.Clone() : @{} return $obj } elseif ($action -eq "parse") { if ([string]::IsNullOrEmpty($data) -or [string]::IsNullOrWhiteSpace($data)) { throw "SyntaxError: codemelted --json Params expects a data key." } return ConvertFrom-Json -InputObject $data -AsHashtable -Depth 100 } elseif ($action -eq "stringify") { if ($null -eq $data) { throw "SyntaxError: codemelted --json Params expects a data key." } if ($data.GetType().Name.ToLower() -eq "arraylist") { return ConvertTo-Json -InputObject $data.ToArray() -Depth 100 } return ConvertTo-Json -InputObject $data -Depth 100 } else { throw "SyntaxError: codemelted json Params did not have a " + "supported action key specified. Valid actions are create_array / " + "create_object / parse / stringify" } } catch [System.FormatException] { return $null } } function codemelted_string_parse { <# .SYNOPSIS Provides the facilities to translate strings into their appropriate data type. SYNTAX: $data = codemelted --string-parse @{ "action" = ""; # required "data" = $dataString # required } ACTIONS: array / boolean / double / int / object RETURNS: [System.Collections.ArrayList] for 'array' action. [boolean] for 'boolean' action. [double] for 'double' action. [int] for 'int' action. [object] for 'object' action. $null for string data that can't be translated. #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) $action = $Params["action"] $data = $Params["data"] if ([string]::IsNullOrEmpty($data) ` -or [string]::IsNullOrWhiteSpace($data)) { throw "SyntaxError: codemelted --string-parse Params expects a " + "data key value." } try { if ($action -eq "array" -or $action -eq "object") { $answer = codemelted_json @{ "action" = "parse"; "data" = $data } return $answer } elseif ($action -eq "boolean") { $trueStrings = @( "true", "1", "t", "y", "yes", "yeah", "yup", "certainly", "uh-huh" ) return $trueStrings.Contains( $data.ToString().ToLower() ) } elseif ($action -eq "double") { return [double]::Parse($data) } elseif ($action -eq "int") { return [int]::Parse($data) } else { throw "SyntaxError: codemelted --string-parse Params did not have a " + "supported action key specified. Valid actions are array / " + "boolean / double / int / object" } } catch [FormatException] { return $null } } # TBD - Storage # TBD - XML # ----------------------------------------------------------------------------- # [NPU Use Cases] ------------------------------------------------------------- # ----------------------------------------------------------------------------- # TBD # ----------------------------------------------------------------------------- # [SDK Use Cases] ------------------------------------------------------------- # ----------------------------------------------------------------------------- function codemelted_logger { <# .SYNOPSIS Provides a logging facility to report log events to STDOUT color coded based on the type of event. Also provides for setting up a handler to perform other types of processing not just to STDOUT. SYNTAX: # Set up the log level for the logging facility within the module. # The "data" levels are 'debug' / 'info' / 'warning' / 'error' / 'off'. codemelted --logger @{ "action" = "log_level"; # required "data" = [string] # optional } # Set up a log handler for logged events via the logger. This will # be called when the logger is not configured as off. Regardless of # the log level, the event will be passed to the handler. The # CLogRecord object will include the current moduleLogLevel. codemelted --logger @{ "action" = "handler"; # required "data" = [scriptblock] / $null # required } # To log data to STDOUT set the "action" to 'debug' / 'info' / # 'warning' / 'error'. If the event meets the module log level, # it will report to STDOUT color coded and time stamped. codemelted --logger @{ "action" = [string] # required "data" = [string] # required } RETURNS: [string] When the action is 'log_level' but no log level is set. [void] For all other actions. #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) $action = $Params["action"] $data = $Params["data"] if ($action -eq "log_level") { if ($null -eq $data) { $logLevelString = [CLogRecord]::logLevelString([CodeMeltedAPI]::logLevel) return $logLevelString } $level = [CLogRecord]::logLevelInt($data) if ($level -eq -1) { throw "SyntaxError: codemelted --logger Params data key not a valid " + "value for setting log level. Valid values are 'debug' / 'info' " + "/ 'warning' / 'error'" } [CodeMeltedAPI]::logLevel = $level } elseif ($action -eq "handler") { if ($null -eq $data) { [CodeMeltedAPI]::logHandler = $null } elseif ($data -is [scriptblock]) { [CodeMeltedAPI]::logHandler = $data } else { throw "SyntaxError: codemelted --logger Params handler action only " + "supports a data value of [null] or [scriptblock]" } } elseif ($action -eq "debug" -or $action -eq "info" -or ` $action -eq "warning" -or $action -eq "error") { if ($Global:logLevel -eq [CLogRecord]::offLogLevel) { return [void] } $level = [CLogRecord]::logLevelInt($action) $record = [CLogRecord]::new($Global:logLevel, $level, $data) if ($Global:logLevel -le [CLogRecord]::debugLogLevel -and ` $level -eq [CLogRecord]::debugLogLevel) { Write-Host $record.ToString() -ForegroundColor White } elseif ($Global:logLevel -le [CLogRecord]::infoLogLevel -and ` $level -eq [CLogRecord]::infoLogLevel) { Write-Host $record.ToString() -ForegroundColor Green } elseif ($Global:logLevel -le [CLogRecord]::warningLogLevel -and ` $level -eq [CLogRecord]::warningLogLevel) { Write-Host $record.ToString() -ForegroundColor Yellow } elseif ($Global:logLevel -le [CLogRecord]::errorLogLevel -and ` $level -eq [CLogRecord]::errorLogLevel) { Write-Host $record.ToString() -ForegroundColor Red } if ($null -ne $Global:logHandler) { Invoke-Command -ScriptBlock $Global:logHandler -ArgumentList $record } } else { throw "SyntaxError: codemelted --slogger Params did not have a " + "supported action key specified. Valid actions are log_level / " + "handler / debug / info / warning / error." } } function codemelted_network { <# .SYNOPSIS Provides a mechanism for getting data within a script via various network calls or setting up a server based network protocol. These are focused around web OSS network technologies and not all the different networking technologies available. SYNTAX: # Perform a network fetch to a REST API. The "data" is a [hashtable] # reflecting the named parameters of the Invoke-WebRequest. So the two # most common items will be "method" / "body" / "headers" but others # are reflected via the link. $resp = codemelted --network @{ "action" = "fetch"; # required "url" = [string / ip]; # required "data" = [hashtable] # required } # FUTURE ACTIONS not implemented yet. http_server / websocket_client / websocket_server RETURNS: [CNetworkResponse] for the 'fetch' action that has the statusCode, statusText, and data as properties. Methods of asBytes(), asObject(), asString() exists to get the data of the expected type or $null if not of that type. .LINK Invoke-WebRequest Details: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) $action = $Params["action"] $url = $Params["url"] # TBD FUTURE $port = $Params["port"] $data = $Params["data"] # Validate the required entries utilized by all actions. if ([string]::IsNullOrEmpty($url) -or [string]::IsNullOrWhiteSpace($url)) { throw "SyntaxError: codemelted --network Params expects a url key with " + "a [string] URL / IP address" } # Go perform the actions. try { if ($action -eq "fetch") { # data needs to be a hashtable or this won't work. if (-not ($data -is [hashtable])) { throw "SyntaxError: codemelted --network Params 'fetch' action " + "expects a data [hashtable] entry." } # Go carry out the fetch. [hashtable] $request = codemelted_json @{ "action" = "create_object"; "data" = $data } $request.Add("uri", $url) $resp = Invoke-WebRequest @request -SkipHttpErrorCheck return [CFetchResponse]::new($resp) } elseif ($action -eq "http_server") { throw "FUTURE IMPLEMENTATION" } elseif ($action -eq "websocket_client") { throw "FUTURE IMPLEMENTATION" } elseif ($action -eq "websocket_server") { throw "FUTURE IMPLEMENTATION" } else { throw "SyntaxError: codemelted --network Params did not have a " + "supported action key specified. Valid actions are fetch / " } } catch { throw "SyntaxError: codemelted --network encountered an issue. " + $_.Exception.Message } } function codemelted_runtime { <# .SYNOPSIS Provides an interface for interacting and learning about the host operating system. SYNTAX: Lookups: # Provides the ability to lookup / run commands with the host # operating system. These actions are 'environment' / 'exist' / # 'system'. These would have a corresponding name identified that # will lookup a variable with the environment, check on a command # that exist, or a system command to execute (to include # parameters to command). $answer = codemelted --runtime @{ "action" = [string] # required action identified above. "name" = [string] # required } Queryable Actions: # Queryable answers to items available about the host operating # system. These actions are 'home_path' / 'hostname' / 'newline' / # 'online' / 'os_name' / 'os_version' / 'path_separator' / # 'processor_count' / 'temp_path' / 'username' $answer = codemelted --runtime @{ "action" = [string] # required action identified above. } Stats: # Gets the current stats of the host operating system. # These actions are 'stats_disk' / 'stats_perf' / 'stats_tcp' / # 'stats_udp' $answer = codemelted --runtime @{ "action" = [string] # required action identified above. } RETURNS: Lookups: [bool] $true if a 'command' action exists. [string] For a environment variable found or $null if not. For a 'system' action, this will be the STDOUT of the command. Queryable Actions: [bool] for the 'online' queryable action. $true means path to Internet. $False means no path to the Internet. [char] for the 'newline' queryable action. [int] for the 'processor_count' queryable action. [string] for all the queryable actions. Stats: # 'stats_disk' action: [hashtable] @{ "bytes_used_percent" = [double]; "bytes_used" = [int]; # kilobytes "bytes_free" = [int]; # kilobytes "bytes_total" = [int]; # kilobytes } # 'stats_perf' action: [hashtable] @{ "cpu_used_percent" = [double]; "cpu_processor_count" = [int]; "mem_used_percent" = [double]; "mem_used_kb" = [int]; "mem_available_kb" = [int]; "mem_total_kb" = [int]; } [string] for 'stats_tcp' / 'stats_udp' action. #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) # Get our expected parameters. $action = $Params["action"] $name = $Params["name"] # Validate name parameter based on actions that would use it. if ($action -eq "environment" -or $action -eq "exist" ` -or $action -eq "system") { if ([string]::isNullOrEmpty($name) -or [string]::IsNullOrWhiteSpace($name)) { throw "SyntaxError: codemelted --runtime $action action expects the " + "name Params key to have a string value." } } # Now go carry out the request. if ($action -eq "environment") { return [System.Environment]::GetEnvironmentVariable($name) } elseif ($action -eq "exist") { if ($IsWindows) { cmd /c where $name > nul 2>&1 } else { which -s $name } return $LASTEXITCODE -eq 0 } elseif ($action -eq "home_path") { return $IsWindows ` ? [System.Environment]::GetEnvironmentVariable("USERPROFILE") : [System.Environment]::GetEnvironmentVariable("HOME") } elseif ($action -eq "hostname") { return [System.Environment]::MachineName } elseif ($action -eq "newline") { return [System.Environment]::NewLine } elseif ($action -eq "online") { return (Test-Connection -Ping google.com -Count 1 -Quiet) } elseif ($action -eq "os_name") { return $IsMacOS ` ? "mac" : $IsWindows ` ? "windows" : (Test-Path "/etc/rpi-issue") ` ? "pi" : "linux" } elseif ($action -eq "os_version") { return [System.Environment]::OSVersion.VersionString } elseif ($action -eq "path_separator") { return [System.IO.Path]::DirectorySeparatorChar } elseif ($action -eq "processor_count") { return [System.Environment]::ProcessorCount } elseif ($action -eq "stats_disk") { $diskStats = (Get-PSDrive -PSProvider FileSystem) $diskTotal = $diskStats[0].Free + $diskStats[0].Used $diskTotalUsedPercent = ($diskStats[0].Used / $diskTotal) * 100 return [ordered] @{ "bytes_used_percent" = $diskTotalUsedPercent; "bytes_used" = $diskStats[0].Used; "bytes_free" = $diskStats[0].Free; "bytes_total" = $diskTotal; } } elseif ($action -eq "stats_perf") { if ($IsWindows) { if ($null -eq $Global:cpuCounter) { $Global:cpuCounter = [System.Diagnostics.PerformanceCounter]::new( "Processor", "% Processor Time", "_Total" ) $Global:cpuCounter.NextValue() | Out-Null } if ($null -eq $Global:memCounter) { $Global:memCounter = [System.Diagnostics.PerformanceCounter]::new( "Memory", "Available MBytes" ) $Global:memCounter.NextValue() | Out-Null } # All memory stats in Kilobytes $availableRam = $Global:memCounter.NextValue() $totalRam = (wmic ComputerSystem get TotalPhysicalMemory | findstr [0..9]) $totalRam = $totalRam.Trim() $totalRam = [double]::Parse($totalRam) / 1e+6 $memUsedKb = $totalRam - $availableRam $memUsed = ($memUsedKb / $totalRam) * 100 return [ordered] @{ "cpu_used_percent" = $Global:cpuCounter.NextValue(); "cpu_processor_count" = [System.Environment]::ProcessorCount; "mem_used_percent" = $memUsed; "mem_used_kb" = $memUsedKb; "mem_available_kb" = $availableRam "mem_total_kb" = $totalRam } } elseif ($IsMacOS) { $cpuLoad = (top -l 1 | grep -E "^CPU" | tail -1 | awk '{ print $3 + $5"%" }').Replace("%", "") $cpuLoad = [double]::Parse($cpuLoad) $usedMemM = (top -l 1 | grep "^Phys" | awk '{ print $2 }').Replace("M", "") $usedMemKb = [double]::Parse($usedMemM) * 1000 $availableMemM = (top -l 1 | grep "^Phys" | awk '{ print $8 }').Replace("M", "") $availableMemKb = [double]::Parse($availableMemM) * 1000 $totalMemKb = $availableMemKb + $usedMemKb $memUsedPercent = ($usedMemKb / $totalMemKb) * 100 return [ordered] @{ "cpu_used_percent" = $cpuLoad; "cpu_processor_count" = [System.Environment]::ProcessorCount; "mem_used_percent" = $memUsedPercent; "mem_used_kb" = $usedMemKb; "mem_available_kb" = $availableMemKb; "mem_total_kb" = $totalMemKb; } } else { $memUsedKb = (free --line | awk '{ print $6}') $memUsedKb = [double]::Parse($memUsedKb) $memFreeKb = (free --line | awk '{ print $8}') $memFreeKb = [double]::Parse($memFreeKb) $memTotalKb = $memFreeKb + $memUsedKb $memUsedPercent = ($memUsedKb / $memTotalKb) * 100 $cpuIdle = (top -b -d1 -n1 | grep Cpu | awk '{ print $8 }') $cpuUsed = 100.0 - [double]::Parse($cpuIdle) return [ordered] @{ "cpu_used_percent" = $cpuUsed; "cpu_processor_count" = [System.Environment]::ProcessorCount; "mem_used_percent" = $memUsedPercent; "mem_used_kb" = $memUsedKb; "mem_available_kb" = $memFreeKb; "mem_total_kb" = $memTotalKb; } } } elseif ($action -eq "stats_tcp") { return $IsLinux ? (netstat -na | grep "tcp") : (netstat -nap tcp) } elseif ($action -eq "stats_udp") { return $IsLinux ? (netstat -na | grep "udp") : (netstat -nap tcp) } elseif ($action -eq "system") { return $IsWindows ? (cmd /c $name) : (sh -c $name) } elseif ($action -eq "temp_path") { return [System.IO.Path]::GetTempPath() } elseif ($action -eq "username") { return $IsWindows ` ? [System.Environment]::GetEnvironmentVariable("USERNAME") : [System.Environment]::GetEnvironmentVariable("USER") } else { throw "SyntaxError: codemelted --runtime Params did not have a " + "supported action key specified. Valid actions are environment / " + "exist / home_path / hostname / newline / online / os_name / " + "os_version / path_separator / processor_count / stats_disk / " + "stats_perf / stats_tcp / stats_udp / system / temp_path / username" } } # ----------------------------------------------------------------------------- # [User Interface Use Cases] -------------------------------------------------- # ----------------------------------------------------------------------------- function codemelted_console { <# .SYNOPSIS Provides a basic mechanism for interacting with a user via STDIN and STDOUT for later processing within a script. SYNTAX: $answer = codemelted --console @{ "action" = ""; # required "message" = ""; # optional message to associate with action "choices" = @( # required when using the 'choose' action. "dog", "cat", "fish" ) } ACTIONS: alert / confirm / choose / password / prompt / writeln RETURNS: alert [void] confirm [boolean] choose [int] password [string] prompt [string] writeln [void] #> param( [Parameter( Mandatory = $true, ValueFromPipeline = $false, Position = 0 )] [hashtable]$Params ) $action = $Params["action"] $message = $Params["message"] $choices = $params["choices"] if ($action -eq "alert") { $prompt = $null -ne $message ? "$message [ENTER]" : "[ENTER]" Read-Host -Prompt $prompt -MaskInput | Out-Null } elseif ($action -eq "confirm") { $prompt = $null -ne $message ? "$message [y/N]" : "CONFIRM [y/N]" $answer = Read-Host -Prompt $prompt return $answer.ToLower() -eq "y" } elseif ($action -eq "choose") { if ((-not ($choices -is [array])) -or $choices.Length -eq 0) { throw "SyntaxError: codemelted --console Params expects a choices " + "key with an array of string values." } [int] $answer = -1 [string] $title = $message ?? "CHOOSE" do { Write-Host "-" * $title.Length Write-Host $title "-" * $title.Length Write-Host [int]$x = 0 foreach ($choice in $choices) { Write-Host "$x. $choice" $x += 1 } Write-Host $selection = Read-Host -Prompt "Make a Selection" try { $answer = [int]::Parse($selection) } catch { Write-Host Write-Warning "Entered value was invalid. Please try again." $answer = -1 } } while ($answer -eq -1) return $answer } elseif ($action -eq "password") { $answer = Read-Host -Prompt ($message ?? "PASSWORD") -MaskInput return $answer } elseif ($action -eq "prompt") { $answer = Read-Host -Prompt ($message ?? "PROMPT") return $answer } elseif ($action -eq "writeln") { Write-Host ($message ?? "") } else { throw "SyntaxError: codemelted --console Params did not have a " + "supported action key specified. Valid actions are alert / " + "confirm / choose / password / prompt / writeln." } } # TBD - Dialog # TBD - SPA # TBD - UI Widgets # ============================================================================= # [MAIN API DEFINITION] ======================================================= # ============================================================================= # OK, go parse the command. switch ($Action) { # Module Information "--version" { Get-PSScriptFileInfo -Path $PSScriptRoot/codemelted.ps1 } "--help" { codemelted_help $Params } # Async I/O Use Cases # Data Use Cases "--data-check" { codemelted_data_check $Params } "--disk" { codemelted_disk $Params } "--json" { codemelted_json $Params } "--string-parse" { codemelted_string_parse $Params } # NPU Use Cases # SDK Use Cases "--logger" { codemelted_logger $Params } "--network" { codemelted_network $Params } "--runtime" { codemelted_runtime $Params } # User Interface Use Case "--console" { codemelted_console $Params } } |