Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI-like interface foundation #11

Draft
wants to merge 23 commits into
base: v2.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "modules/Git/posh-git"]
path = modules/Git/posh-git
url = https://github.com/dahlbyk/posh-git.git
[submodule "modules/Cli/ArgParser"]
path = modules/Cli/ArgParser
url = https://github.com/buriedstpatrick/ArgParser
1 change: 1 addition & 0 deletions modules/AutoHotkey/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.ahk
54 changes: 54 additions & 0 deletions modules/AutoHotkey/AutoHotkey.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
function Invoke-AutoHotkeyKeybindings($config) {
Get-ChildItem (Join-Path $env:PWSH_REPO "modules" "AutoHotkey" "*.ahk.TEMPLATE") | ForEach-Object {
$destination = $_.FullName.Replace(".TEMPLATE", "")
if (!(Test-Path $destination)) {
Get-Content $_.FullName | Format-EnvironmentVariables | Format-ConfigValues -Config (Get-Config) | Out-File $destination
}
}

if ($config.loadKeybindsOnStartup -and !(Test-Path (Join-Path $env:APPDATA "Microsoft" "Windows" "Start Menu" "Programs" "Startup" "Keybinds.ahk"))) {
Install-AutoHotkeyKeybindings
}
}

function Install-AutoHotkeyKeybindings {
$startupDir = (Join-Path $env:APPDATA "Microsoft" "Windows" "Start Menu" "Programs" "Startup")

if (!(Test-IsAdministrator)) {
Write-ErrorMessage "Unable to install AutoHotkey keybinds. Please run your as administrator to install."
return
}

# Create symbolic links in startup directory
Get-ChildItem -Exclude *.TEMPLATE (Join-Path $env:PWSH_REPO "modules" "AutoHotkey" "*.ahk") | ForEach-Object {
$destination = (Join-Path $startupDir $_.Name)
if (!(Test-Path $destination)) {
Write-InfoMessage "Installing $($_.Name)"

New-Item -ItemType SymbolicLink `
-Path $destination `
-Value $_.FullName
}
}
}

function Uninstall-AutoHotkeyKeybindings {
if (!(Test-IsAdministrator)) {
Write-ErrorMessage "Unable to uninstall AutoHotkey keybinds. Please run this command in an administrator context."
return
}

$destination = (Join-Path $env:APPDATA "Microsoft" "Windows" "Start Menu" "Programs" "Startup" "Keybinds.ahk")
if (!(Test-Path $destination)) {
Write-ErrorMessage "Unable to uninstall AutoHotkey keybinds. The symbolic link does not exist."
return
}

Remove-Item $destination
}

$config = (Get-Config).modules.AutoHotkey

if ($config.enabled -and !($null -eq $config.config)) {
Invoke-AutoHotkeyKeybindings $config.config
}
17 changes: 17 additions & 0 deletions modules/AutoHotkey/Keybinds.ahk.TEMPLATE
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#SingleInstance,Force

; Open Windows Terminal on Windows+Enter
#{modules.AutoHotkey.config.binds.terminal}::
Run, "#{terminal.path}" #{terminal.args}
return

; Open Slack on Super+S
LWin & s::
Run, "${LOCALAPPDATA}\slack\slack.exe"
return

; Stop keybinds
#{modules.AutoHotkey.config.binds.stop}::ExitApp

; Reload keybinds
#{modules.AutoHotkey.config.binds.reload}::Reload
9 changes: 9 additions & 0 deletions modules/AutoHotkey/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# AutoHotkey keybinds

Adds a couple of global keybinds that work outside of the terminal context. AutoHotkey is required.

| Keybind | Action |
|---------------|------------------------------------------------------------|
| `Super+Enter` | Open terminal instance (Configure terminal in `pwsh.yaml`) |
| `Super+Q` | Stop all keybinds |
| `Super+R` | Re-load all keybinds |
1 change: 1 addition & 0 deletions modules/Cli/ArgParser
Submodule ArgParser added at 41e57a
91 changes: 91 additions & 0 deletions modules/Cli/Cli.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
function New-ProfileAlias {
Write-Host "hello!"
}

$commands = @{
"alias" = @{
"add" = "New-ProfileAlias"
}
"keybinds" = @{
"install" = "Install-AutoHotkeyKeybindings"
"uninstall" = "Uninstall-AutoHotkeyKeybindings"
}
}

function Invoke-LoadArgParser {
# Check that module was fetched via Git. If not, update submodules.
if (!(Test-Path (Join-Path $env:PWSH_REPO "modules" "Cli" "ArgParser" "Output" "ArgParser" "ArgParser.psd1"))) {
Push-Location $env:PWSH_REPO
try {
git submodule init
git submodule update
} finally {
Pop-Location
}
}

# Check that module was build. If not, run build
if (!(Test-Path (Join-Path $env:PWSH_REPO "modules" "Cli" "ArgParser" "Output" "ArgParser" "ArgParser.psd1"))) {
Push-Location (Join-Path $env:PWSH_REPO "modules" "Cli" "ArgParser")
try {
.\Install-Requirements.ps1
.\Start-ModuleBuild.ps1
} finally {
Pop-Location
}
}

# Import the module
Import-Module (Join-Path $env:PWSH_REPO "modules" "Cli" "ArgParser" "Output" "ArgParser")
}

function Invoke-ProfileCli {
# Load ArgParser module
Invoke-LoadArgParser

# Parse arguments into an array, assume arguments space delimited
try {
$arguments = $args.Split(' ')
} catch {
# Unable to parse arguments which probably means nothing was entered beyond the root command 'profile'
# Print a user-friendly message, perhaps the help documentation
Write-Host "Usage: profile <command> <flags>"
return
}

# Hack: Force $arguments to be an array even if it's only a single entry.
# PowerShell treats single entry arrays as strings otherwise.
if (($arguments -is [string])) {
$arguments = @($arguments)
}

# Print the version of the CLI if requested
if (Get-ArgParserHasSwitch -Name "version" -ShortName "v" -Arguments $arguments) {
Write-Host "Version: 1.0.0.0"
}

# Print helpful documentation of the CLI if requested, break out of application flow
if (Get-ArgParserHasSwitch -Name "help" -ShortName "h" -Arguments $arguments) {
Write-Help @args
return
}

$scope = Get-ArgParserScope -Arguments $arguments

# $name = Get-ArgParserStringValue -Name "name" -ShortName "n" -Arguments $arguments
# if ($name) {
# Write-Host "Hello, $name! You have been reported to the authorities, have a great day :D"
# }

$index = 0
$command = $commands
$scope | ForEach-Object {
$command = $command[$_]
$index++
} | Select-Object -Last 1

$command | Invoke-Expression
}

Set-Alias profile Invoke-ProfileCli
Export-ModuleMember -Function Invoke-ProfileCli -Alias profile
50 changes: 50 additions & 0 deletions modules/Cli/Write-Help.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
function Write-Help {
$action = (Get-Content (Join-Path $env:PWSH_REPO "modules" "Cli" "actions.json") | ConvertFrom-Json)
$args | Where-Object { !($_ -like '-*') } | ForEach-Object {
$action = $action."$_"
}

$helpText = $action.helpText
if(!($null -eq $helpText)) {
Write-Output "Description:"
Write-Output ("`t$helpText" | Format-EnvironmentVariables)
}

$flags = $action.flags
if(!($null -eq $flags) -and ($flags.Length -gt 0)) {
Write-Output "Flags:"
$flags.PSObject.Properties | ForEach-Object {
$required = $_.Value.required -eq $true ? " [REQUIRED]" : ""

$shortName = $_.Value.shortName ? ", -$($_.Value.shortName)" : $null
Write-Output "`t--$($_.Name)$shortName [$($_.Value.type)]$required"

if ($_.Value.helpText) {
Write-Output ("`t`t$($_.Value.helpText)" | Format-EnvironmentVariables)
}

if ($_.Value.default) {
Write-Output ("`t`tDefault: $($_.Value.default)" | Format-EnvironmentVariables)
}
}
}

$examples = $action.examples
if(!($null -eq $examples) -and ($examples.Length -gt 0)) {
Write-Output "Examples:"
$examples | ForEach-Object {
Write-Output ("`t$_" | Format-EnvironmentVariables)
}
}

$commands = $action.PSObject.Properties | Where-Object {
!($_.Name -like 'helpText') -and !($_.Name -like 'examples') -and !($_.Name -like 'flags')
}

if ($commands.Length -gt 0) {
Write-Output "Commands:"
$commands | ForEach-Object {
Write-Output ("`t$($_.Name) - $($_.Value.helpText)" | Format-EnvironmentVariables)
}
}
}
81 changes: 81 additions & 0 deletions modules/Cli/actions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"helpText": "Manage PWSH profile. Use --help for more information about a command scope.",
"config": {
"helpText": "Manage configuration",
"get": {
"helpText": "Display current config"
},
"find": {
"helpText": "Output the path to the config file"
},
"edit": {
"helpText": "Edit config with default configured editor (${EDITOR})"
}
},
"update" : {
"helpText": "Update the PWSH profile to the latest commit on your currently configured branch."
},
"reload": {
"helpText": "Reload profile"
},
"alias": {
"helpText": "Manage aliases for this CLI",
"add": {
"helpText": "Add an alias to the profile",
"flags": {
"name": {
"helpText": "Name of the alias",
"type": "string",
"required": true
}
}
},
"remove": {
"helpText": "Remove a path from the profile",
"flags": {
"name": {
"helpText": "Name of the alias to remove",
"type": "string",
"required": true
}
}
},
"list": {
"helpText": "List configured profile aliases"
}
},
"path": {
"helpText": "Manage paths",
"add": {
"helpText": "Add a path to the profile",
"flags": {
"path": {
"helpText": "Path to add",
"type": "string",
"required": true
}
}
},
"remove": {
"helpText": "Remove a path from the profile",
"flags": {
"path": {
"helpText": "Path to remove",
"type": "string",
"required": true
}
}
},
"list": {
"helpText": "List profile paths",
"flags": {
"all": {
"helpText": "Print entire path list, not just locally configured in pwsh.yaml",
"shortName": "a",
"type": "switch",
"default": false
}
}
}
}
}
40 changes: 35 additions & 5 deletions modules/Core/Config.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ function Get-Config {

$config = $config | Format-EnvironmentVariables | Format-Paths

$config | Where-Object { !($_ -ilike '*#*') -and $_ } > $env:TMP\pwsh.yaml
$config = (ConvertFrom-Yaml -Path $env:TMP\pwsh.yaml)
$hash = Get-Random
$env:TMP = $env:TMP ?? (Join-Path $HOME "tmp")
if (!(Test-Path $env:TMP)) {
New-Item -ItemType Directory -Force -Path $env:TMP
}

$tempConfigPath = (Join-Path $env:TMP "pwsh.$hash.yaml")

Remove-Item $env:TMP\pwsh.yaml
$config | Where-Object { !($_ -ilike '*#*') -and $_ } | Out-File -Path $tempConfigPath
$config = (ConvertFrom-Yaml -Path $tempConfigPath)

Remove-Item $tempConfigPath

return $config
}
Expand All @@ -35,9 +43,31 @@ function Edit-UserConfig {
"$env:EDITOR $env:PWSH_CONFIG" | Invoke-Expression
}

function Edit-Configs {
# Paths to potential config files
$configs = @(
(Join-Path $env:APPDATA "alacritty" "alacritty.yml"),
(Get-ChildItem (Join-Path $env:LOCALAPPDATA "Packages" "Microsoft.WindowsTerminal_*" "LocalState" "settings.json")).FullName),
(Join-Path $env:HOME ".gitconfig"),
(Join-Path $env:HOME ".config" "starship.toml")
| Where-Object { Test-Path $_ }

$config = Read-Option "Select config to edit" $configs

if ($config) {
"$env:EDITOR $config" | Invoke-Expression
}
}

# Keybinds
Set-PSReadLineKeyHandler -Chord Ctrl+. -Description "Edit pwsh.yaml config with $env:EDITOR" -ScriptBlock {
Set-PSReadLineKeyHandler -Chord "Ctrl+." -Description "Edit pwsh.yaml config with $env:EDITOR" -ScriptBlock {
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert('Edit-UserConfig')
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}
}

Set-PSReadLineKeyHandler -Chord Ctrl+e -Description "Pick a config file to edit with $env:EDITOR" -ScriptBlock {
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert('Edit-Configs')
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}
Loading