class REPLCommand {
[string] $Name
[scriptblock] $Action
[string] $Description
REPLCommand() {
$this.Name = $this.GetType().Name
$this.Action = {}
$this.Description = ""
}
REPLCommand([string] $name, [scriptblock] $action, [string] $description) {
$this.Name = $name
$this.Action = $action
$this.Description = $description
}
[object] Execute([string] $input) {
return Invoke-Command -ScriptBlock $this.Action -ArgumentList $input
}
[SimpleREPL] Repl() {
return [SimpleREPL]::GetInstance()
}
[string] GetValue([string] $originValue, [string] $defaultVariable) {
return $this.GetValue($originValue, $defaultVariable, "")
}
[string] GetValue([string] $originValue, [string] $defaultVariable, [string] $defaultValue) {
if ($originValue) {
return $originValue
}
$value = $this.Repl().GetVariable($defaultVariable)
if ($value) {
return $value
}
return $defaultValue
}
}
class Echo: REPLCommand {
Echo() {
$this.Description = "回显输入内容。"
$this.Action = {
param([string] $input)
Write-Host $input
}
}
}
class Exit: REPLCommand {
Exit() {
$this.Description = "退出 REPL。"
$this.Action = {
param([string] $input)
[SimpleREPL]::GetInstance().Running = false
}
}
}
class Quit: REPLCommand {
Quit() {
$this.Description = "退出 REPL。"
$this.Action = {
param([string] $input)
[SimpleREPL]::GetInstance().Running = false
}
}
}
class Clear: REPLCommand {
Clear() {
$this.Description = "清空屏幕。"
$this.Action = {
Clear-Host
}
}
}
class Help: REPLCommand {
Help() {
$this.Description = "展示帮助信息。"
$this.Action = {
param([string] $input)
if ($input -and $this.Repl().Commands.ContainsKey($input)) {
$command = $this.Repl().Commands[$input]
Write-Host ("{0,-8} {1}" -f $command.Name.ToLower(), $command.Description)
return
}
$this.Repl().Commands.GetEnumerator() | Sort-Object Name | ForEach-Object {
Write-Host ("{0,-8} {1}" -f $_.Value.Name.ToLower(), $_.Value.Description)
}
}
}
}
class Time: REPLCommand {
Time() {
$this.Description = "显示系统时间。"
$this.Action = {
param([string] $input)
Write-Host "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
}
}
}
class PSVersion: REPLCommand {
PSVersion() {
$this.Description = "显示 PowerShell 版本。"
$this.Action = {
param([string] $input)
Write-Host $PSVersionTable.PSVersion.ToString()
}
}
}
class Set: REPLCommand {
Set() {
$this.Description = "设置变量值。用法:set a 1"
$this.Action = {
param([string] $input)
$parts = $input.Trim() -split '\s+', 2
$var = $parts[0]
$value = $parts[1]
$this.Repl().SetVariable($var, $value)
}
}
}
class Get: REPLCommand {
Get() {
$this.Description = "查看变量值。用法:get [variable]"
$this.Action = {
param([string] $input)
$parts = $input.Trim() -split '\s+'
if (-not $name) {
$this.Repl().Variables.GetEnumerator() | Sort-Object Name | ForEach-Object {
Write-Host ("{0,-8} {1}" -f $_.Key, $_.Value)
}
return
}
$name = $parts[0]
$value = $this.Repl().GetVariable($name)
Write-Host $value
}
}
}
class Jsonrpc2: REPLCommand {
Jsonrpc2() {
$this.Description = "发送 JSON-RPC2 请求。用法:jsonrpc2 [url] [method] [params] [id]。如果没有传入参数,从变量表中获取对应的值。"
$this.Action = {
param([string] $input)
$parts = $input.Trim() -split '\s+', 4
$url = $this.GetValue($parts[0], "url")
$method = $this.GetValue($parts[1], "method")
$params = $this.GetValue($parts[2], "params")
$id = $this.GetValue($parts[3], "id", "1")
if ((-not $url) -or (-not $method)) {
Write-Host "错误:缺少 url 和 method 参数。"
return
}
$params = $params | ConvertFrom-Json -AsHashtable
$response = ($this.InvokeJsonRpc2($url, $method, $params, $id) | ConvertTo-Json).Trim()
Write-Host $response
}
}
[object] InvokeJsonRpc2([string] $url, [string] $method, [object] $params, [string] $id) {
# 构建 JSON-RPC 2.0 请求对象
$req = @{
jsonrpc = "2.0"
method = $Method
id = $Id
params = @{}
}
# 如果提供了 params,则添加到请求中
if ($null -ne $Params) {
$req.params = $Params
}
# 转换为 JSON 字符串
$body = $req | ConvertTo-Json
Write-Verbose "Sending JSON-RPC request: $body"
try {
return Invoke-RestMethod -Uri $url -Method POST -ContentType "application/json" -Body $body
}
catch {
Write-Error "JSON-RPC call failed: $($_.Exception.Message)"
throw
}
}
}
class Save: REPLCommand {
Save() {
$this.Description = "将变量值保存到 .repl.var.json 文件。"
$this.Action = {
param([string] $input)
$this.Repl().VariableFile.Write($this.Repl().Variables)
}
}
}
class Load: REPLCommand {
Load() {
$this.Description = "从 .repl.var.json 文件加载变量值。"
$this.Action = {
param([string] $input)
$table = $this.Repl().VariableFile.Read()
foreach ($key in $table.Keys) {
$this.Repl().Variables[$key] = $table[$key]
}
}
}
}
class Config: REPLCommand {
Config() {
$this.Description = "查看或修改配置,并保存到 .repl.cfg.json 文件。"
$this.Action = {
param([string] $input)
if ($this.Repl().Configs.Count -eq 0) {
$this.Repl().Configs = $this.Repl().ConfigFile.Read()
}
$parts = $input.Trim() -split '\s+', 2
$name = $parts[0]
$value = $parts[1]
if (-not $name) {
$this.Repl().Configs.GetEnumerator() | Sort-Object Name | ForEach-Object {
Write-Host ("{0,-8} {1}" -f $_.Key, $_.Value)
}
return
}
if (-not $value) {
$value = $this.Repl().Configs[$name]
Write-Host $value
return
}
$this.Repl().Configs[$name] = $value
$this.Repl().ConfigFile.Write($this.Repl().Configs)
}
}
}
class Script: REPLCommand {
Script() {
$this.Description = "显示 repl 脚本。"
$this.Action = {
param([string] $input)
$files = Get-ChildItem -Filter *.repl
foreach ($file in $files) {
Write-Host $file.BaseName
}
}
}
}
class Run: REPLCommand {
Run() {
$this.Description = "执行 repl 脚本。"
$this.Action = {
param([string] $input)
$scriptFileName = $input
if (-not $input.EndsWith(".repl")) {
$scriptFileName += ".repl"
}
$lines = Get-Content $scriptFileName | ForEach-Object {
($_ -replace '#.*$').Trim()
} | Where-Object { $_ -ne "" }
$list = [Collections.Generic.List[string]]$lines
$this.Repl().ExecuteBatch($list)
}
}
}
class ReplJsonFile {
[string]$FilePath
ReplJsonFile([string]$customPath) {
$this.FilePath = $customPath
}
[hashtable] Read() {
if (-not (Test-Path $this.FilePath)) {
return @{}
}
try {
$jsonContent = Get-Content -Path $this.FilePath -Raw -ErrorAction Stop
$hashtable = $jsonContent | ConvertFrom-Json -AsHashtable -ErrorAction Stop
return $hashtable
}
catch {
throw "读取文件失败: $($_.Exception.Message)"
}
}
[void] Write([hashtable]$data) {
try {
$jsonContent = $data | ConvertTo-Json -Depth 10 -Compress
$jsonContent | Set-Content -Path $this.FilePath -Encoding UTF8 -Force
}
catch {
throw "写入文件失败: $($_.Exception.Message)"
}
}
[void] Clear() {
if (Test-Path $this.FilePath) {
Remove-Item $this.FilePath -Force
}
}
[bool] Exists() {
return (Test-Path $this.FilePath)
}
}
class SimpleREPL {
hidden static [SimpleREPL] $instance = $null
# 获取单例实例
static [SimpleREPL] GetInstance() {
if (-not [SimpleREPL]::instance) {
[SimpleREPL]::instance = [SimpleREPL]::new()
}
return [SimpleREPL]::instance
}
[hashtable] $Commands = @{}
[bool] $Running = $false
[hashtable] $Variables = @{}
[hashtable] $Configs = @{}
[ReplJsonFile] $VariableFile
[ReplJsonFile] $ConfigFile
hidden SimpleREPL() {
$this.ScanCommand()
$path = Join-Path (Get-Location) ".repl.var.json"
$this.VariableFile = [ReplJsonFile]::new($path)
$path = Join-Path (Get-Location) ".repl.cfg.json"
$this.ConfigFile = [ReplJsonFile]::new($path)
$this.Configs = $this.ConfigFile.Read()
}
# 防止克隆
hidden [SimpleREPL] Clone() {
return $this
}
[string] getConfig([string] $name) {
return $this.Configs[$name]
}
[boolean] isConfigMatch([string] $name, [string] $target) {
return $this.getConfig($name).Equals($target)
}
[void] SetVariable([string] $var, [string] $value) {
$this.Variables[$var] = $value
}
[string] GetVariable([string] $var) {
return $this.Variables[$var]
}
[void] ScanCommand() {
# 获取当前 AppDomain 中的所有类型。
$allTypes = [AppDomain]::CurrentDomain.GetAssemblies() |
ForEach-Object { $_.GetTypes() } |
Where-Object { $_.IsClass -and !$_.IsAbstract } |
Group-Object FullName | ForEach-Object { $_.Group[0] }
# 扫描 REPLCommand 派生类并注册。
foreach ($type in $allTypes) {
if ($type.BaseType -and $type.BaseType.Name -eq "REPLCommand") {
try {
$command = New-Object -TypeName $type.FullName
$this.RegisterCommand($command)
} catch {
# ignore
}
}
}
}
[void] RegisterCommand([REPLCommand] $command) {
$LowerName = $command.GetType().Name.ToLower()
$this.Commands[$LowerName] = $command
}
[void]RegisterCommand([string]$name, [scriptblock]$action, [string]$description) {
$command = [REPLCommand]::new($name, $action, $description)
$this.Commands[$name] = $command
}
[hashtable] ParseInput([string] $input) {
$parts = $input.Trim() -split '\s+', 2
$commandName = $parts[0].ToLower()
$arguments = $parts[1]
return @{
Command = $commandName
Arguments = $arguments
}
}
[void] Run() {
$this.Running = $true
Write-Host "PowerShell REPL" -ForegroundColor Cyan
Write-Host "输入 'help' 查看可用命令" -ForegroundColor Yellow
Write-Host "输入 'quit' 或 'exit' 退出" -ForegroundColor Yellow
while ($this.Running) {
try {
$input = Read-Host "repl"
if ([string]::IsNullOrWhiteSpace($input)) {
continue
}
$this.Execute($input)
} catch {
Write-Host "执行错误: $($_.Exception.Message)" -ForegroundColor Red
}
}
}
[void] Execute([string] $input) {
$startTime = Get-Date
$measureTime = $false
if ($this.isConfigMatch("measure.time", "on")) {
$measureTime = $true
}
try {
$parsed = $this.ParseInput($input)
$commandName = $parsed.Command.ToLower()
$arguments = $parsed.Arguments
if ($this.Commands.ContainsKey($commandName)) {
$this.Commands[$commandName].Execute($arguments)
return
}
# 如果不是注册的命令,尝试作为 PowerShell 命令执行
try {
$result = (Invoke-Expression $input | Out-String).Trim()
if ($result) {
Write-Host $result
}
} catch {
Write-Host "错误: 未知命令 '$commandName'" -ForegroundColor Red
Write-Host "输入 'help' 查看可用命令" -ForegroundColor Yellow
}
} finally {
if ($measureTime) {
Write-Host "----------------"
$endTime = Get-Date
$duration = New-TimeSpan -Start $startTime -End $endTime
$milliseconds = $duration.TotalMilliseconds
$min = [int]($milliseconds / 60000)
$sec = [int]($milliseconds / 1000)
$millis = [int]($milliseconds % 1000)
$timeMessage = ""
if ($min -gt 0) {
$timeMessage += "$min 分"
}
if ($sec -gt 0) {
$timeMessage += " $sec 秒"
}
if ($timeMessage.length -ne 0) {
$timeMessage += " "
}
$timeMessage += "$millis 毫秒"
Write-Host "耗时 $timeMessage"
}
}
}
[void] ExecuteBatch([System.Collections.Generic.List[string]] $batch) {
foreach ($line in $batch) {
if ([string]::IsNullOrWhiteSpace($line)) {
continue
}
$this.Execute($line)
}
}
}
# 使用示例和演示
function Start-REPL {
[SimpleREPL]::GetInstance().Run()
}
Start-REPL
# repl 脚本示例
set url http://localhost:8080/rpc
set method system/info
jsonrpc2
优点
- 提供丰富的内置命令(echo、help、time、set/get等)
- 支持变量持久化(save/load命令)
- 支持配置文件管理(config命令)
- 支持脚本批量执行(run命令)
- 代码架构设计良好,便于添加新命令
- 实现了 JSON-RPC 2.0 基础功能
缺点
- 错误处理不够完善
- 安全性考虑不足
- 缺乏请求/响应日志记录功能
- 配置系统相对简单,缺乏类型验证