From ffff67de3076674c869f60738ac853b56f2efb76 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 9 Mar 2022 22:42:40 +0100 Subject: [PATCH 001/100] initial commit --- .gitignore | 450 ++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++ README.md | 3 + src/Program.cs | 100 +++++++++ src/ScriptLauncher.csproj | 16 ++ 5 files changed, 590 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/Program.cs create mode 100644 src/ScriptLauncher.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ca3ba7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,450 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4fffa0c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Marcello Lamonaca + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..438a0fd --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Script Launcher + +Find executable scripts in a directory and allow to select which one to execute diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..c3a7b5b --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,100 @@ +using System.Diagnostics; +using Cocona; +using Spectre.Console; + +var app = CoconaLiteApp.Create(); + +app.AddCommand(RootCommand); +app.Run(); + +static void RootCommand( + [Option("dir", new[] { 'd' }, Description = "Directory from which search the scripts")] string directory = ".", + [Option("ext", Description = "Comma separated list of script extensions to search")] string extensions = "*", + [Option("elevated", new[] { 'e' }, Description = "Run the script with elevated privileges")] bool elevated = false, + [Option("depth")] int depth = 0) +{ + if (!Directory.Exists(directory)) + { + AnsiConsole.Markup($"[red]The directory {directory} does not exist.[/]"); + Environment.Exit(1); + } + + var fileExtensions = extensions.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .ToArray(); + + var finder = new ScriptFinder() + { + Depth = depth, + Extensions = fileExtensions, + RootFolder = directory, + }; + + var files = finder.GetScriptFiles(fileExtensions); + + if (files.Length == 0) + { + AnsiConsole.Markup($"[red]No scripts script files found in {directory} with extensions '{string.Join("|", fileExtensions)}'[/]"); + Environment.Exit(1); + } + + var prompt = new SelectionPrompt() + .Title("Select a script") + .AddChoices(files.Select(x => x.FullName)); + + var script = AnsiConsole.Prompt(prompt); + + ScriptExecutor.Exec(script); +}; + +static class ScriptExecutor +{ + internal static void Exec(string filename) + { + var info = new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-File {filename}", + UseShellExecute = false, + }; + + Exec(info); + } + + internal static void Exec(ProcessStartInfo info) => Process.Start(info)?.WaitForExit(); +} + +class ScriptFinder +{ + public string RootFolder { get; set; } = "."; + public string[] Extensions { get; set; } = new[] { "ps1", "*sh", "bat", "cmd" }; + public int Depth { get; set; } = 0; + + private readonly EnumerationOptions _options; + + public ScriptFinder() + { + _options = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; + } + + internal FileInfo[] GetScriptFiles(string extension) + { + try + { + var filenames = Directory.GetFiles(RootFolder, $"*.{extension}", _options); + return filenames.Select(x => new FileInfo(x)).ToArray(); + } + catch (UnauthorizedAccessException) + { + return Array.Empty(); + } + } + + internal FileInfo[] GetScriptFiles(string[] extensions) => + extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); +} \ No newline at end of file diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj new file mode 100644 index 0000000..febac53 --- /dev/null +++ b/src/ScriptLauncher.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + 0.1.0 + + + + + + + + From 190953f76b4da179bd909d27a80b933441e9a2dc Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 9 Mar 2022 22:42:40 +0100 Subject: [PATCH 002/100] Initail Commit --- .gitignore | 450 ++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++ README.md | 3 + src/Program.cs | 100 +++++++++ src/ScriptLauncher.csproj | 16 ++ 5 files changed, 590 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/Program.cs create mode 100644 src/ScriptLauncher.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ca3ba7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,450 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4fffa0c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Marcello Lamonaca + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..438a0fd --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Script Launcher + +Find executable scripts in a directory and allow to select which one to execute diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..c3a7b5b --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,100 @@ +using System.Diagnostics; +using Cocona; +using Spectre.Console; + +var app = CoconaLiteApp.Create(); + +app.AddCommand(RootCommand); +app.Run(); + +static void RootCommand( + [Option("dir", new[] { 'd' }, Description = "Directory from which search the scripts")] string directory = ".", + [Option("ext", Description = "Comma separated list of script extensions to search")] string extensions = "*", + [Option("elevated", new[] { 'e' }, Description = "Run the script with elevated privileges")] bool elevated = false, + [Option("depth")] int depth = 0) +{ + if (!Directory.Exists(directory)) + { + AnsiConsole.Markup($"[red]The directory {directory} does not exist.[/]"); + Environment.Exit(1); + } + + var fileExtensions = extensions.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .ToArray(); + + var finder = new ScriptFinder() + { + Depth = depth, + Extensions = fileExtensions, + RootFolder = directory, + }; + + var files = finder.GetScriptFiles(fileExtensions); + + if (files.Length == 0) + { + AnsiConsole.Markup($"[red]No scripts script files found in {directory} with extensions '{string.Join("|", fileExtensions)}'[/]"); + Environment.Exit(1); + } + + var prompt = new SelectionPrompt() + .Title("Select a script") + .AddChoices(files.Select(x => x.FullName)); + + var script = AnsiConsole.Prompt(prompt); + + ScriptExecutor.Exec(script); +}; + +static class ScriptExecutor +{ + internal static void Exec(string filename) + { + var info = new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-File {filename}", + UseShellExecute = false, + }; + + Exec(info); + } + + internal static void Exec(ProcessStartInfo info) => Process.Start(info)?.WaitForExit(); +} + +class ScriptFinder +{ + public string RootFolder { get; set; } = "."; + public string[] Extensions { get; set; } = new[] { "ps1", "*sh", "bat", "cmd" }; + public int Depth { get; set; } = 0; + + private readonly EnumerationOptions _options; + + public ScriptFinder() + { + _options = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; + } + + internal FileInfo[] GetScriptFiles(string extension) + { + try + { + var filenames = Directory.GetFiles(RootFolder, $"*.{extension}", _options); + return filenames.Select(x => new FileInfo(x)).ToArray(); + } + catch (UnauthorizedAccessException) + { + return Array.Empty(); + } + } + + internal FileInfo[] GetScriptFiles(string[] extensions) => + extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); +} \ No newline at end of file diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj new file mode 100644 index 0000000..febac53 --- /dev/null +++ b/src/ScriptLauncher.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + 0.1.0 + + + + + + + + From ae80e950bf4d03474718e9c7fd2c85b785f101ee Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 16:32:53 +0100 Subject: [PATCH 003/100] Allow execution of multiple scripts --- src/Program.cs | 71 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index c3a7b5b..d597268 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -2,59 +2,84 @@ using Cocona; using Spectre.Console; +const int ScriptListSize = 15; +const int ErrorExitCode = 1; + var app = CoconaLiteApp.Create(); app.AddCommand(RootCommand); app.Run(); static void RootCommand( - [Option("dir", new[] { 'd' }, Description = "Directory from which search the scripts")] string directory = ".", - [Option("ext", Description = "Comma separated list of script extensions to search")] string extensions = "*", - [Option("elevated", new[] { 'e' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("depth")] int depth = 0) + [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", + [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, + [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, + [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts")] bool multiple = false, + [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) { - AnsiConsole.Markup($"[red]The directory {directory} does not exist.[/]"); - Environment.Exit(1); + AnsiConsole.Markup($"[red]The directory '{directory}' does not exist.[/]"); + Environment.ExitCode = ErrorExitCode; + return; } var fileExtensions = extensions.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) .ToHashSet() .ToArray(); - var finder = new ScriptFinder() + var finder = new ScriptFinder { - Depth = depth, Extensions = fileExtensions, RootFolder = directory, + Depth = depth }; var files = finder.GetScriptFiles(fileExtensions); if (files.Length == 0) { - AnsiConsole.Markup($"[red]No scripts script files found in {directory} with extensions '{string.Join("|", fileExtensions)}'[/]"); - Environment.Exit(1); + AnsiConsole.Markup($"[red]No scripts script files found in '{directory}' with extensions '{string.Join("|", fileExtensions)}'[/]"); + Environment.ExitCode = ErrorExitCode; + return; } - var prompt = new SelectionPrompt() + if (multiple) + { + var prompt = new MultiSelectionPrompt() + .Title("Select scripts") + .NotRequired() + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .AddChoices(files); + + var scripts = AnsiConsole.Prompt(prompt); + + scripts.ForEach(ScriptExecutor.Exec); + } + else + { + var prompt = new SelectionPrompt() .Title("Select a script") - .AddChoices(files.Select(x => x.FullName)); + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .AddChoices(files); - var script = AnsiConsole.Prompt(prompt); + var script = AnsiConsole.Prompt(prompt); - ScriptExecutor.Exec(script); + ScriptExecutor.Exec(script); + } }; static class ScriptExecutor { - internal static void Exec(string filename) + internal static void Exec(FileInfo file) { var info = new ProcessStartInfo { FileName = "powershell.exe", - Arguments = $"-File {filename}", + Arguments = $"-File {file.FullName}", UseShellExecute = false, }; @@ -64,11 +89,11 @@ static class ScriptExecutor internal static void Exec(ProcessStartInfo info) => Process.Start(info)?.WaitForExit(); } -class ScriptFinder +readonly struct ScriptFinder { - public string RootFolder { get; set; } = "."; - public string[] Extensions { get; set; } = new[] { "ps1", "*sh", "bat", "cmd" }; - public int Depth { get; set; } = 0; + public string RootFolder { get; init; } = "."; + public string[] Extensions { get; init; } = new[] { "ps1", "*sh", "bat", "cmd" }; + public int Depth { get; init; } = 0; private readonly EnumerationOptions _options; @@ -82,7 +107,7 @@ class ScriptFinder }; } - internal FileInfo[] GetScriptFiles(string extension) + internal readonly FileInfo[] GetScriptFiles(string extension) { try { @@ -95,6 +120,6 @@ class ScriptFinder } } - internal FileInfo[] GetScriptFiles(string[] extensions) => - extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); + internal readonly FileInfo[] GetScriptFiles() => + Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); } \ No newline at end of file From 01b62cd88249ee4ca971130f9aff840b2951190f Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 16:32:53 +0100 Subject: [PATCH 004/100] Allow execution of multiple scripts --- src/Program.cs | 71 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index c3a7b5b..d597268 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -2,59 +2,84 @@ using Cocona; using Spectre.Console; +const int ScriptListSize = 15; +const int ErrorExitCode = 1; + var app = CoconaLiteApp.Create(); app.AddCommand(RootCommand); app.Run(); static void RootCommand( - [Option("dir", new[] { 'd' }, Description = "Directory from which search the scripts")] string directory = ".", - [Option("ext", Description = "Comma separated list of script extensions to search")] string extensions = "*", - [Option("elevated", new[] { 'e' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("depth")] int depth = 0) + [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", + [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, + [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, + [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts")] bool multiple = false, + [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) { - AnsiConsole.Markup($"[red]The directory {directory} does not exist.[/]"); - Environment.Exit(1); + AnsiConsole.Markup($"[red]The directory '{directory}' does not exist.[/]"); + Environment.ExitCode = ErrorExitCode; + return; } var fileExtensions = extensions.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) .ToHashSet() .ToArray(); - var finder = new ScriptFinder() + var finder = new ScriptFinder { - Depth = depth, Extensions = fileExtensions, RootFolder = directory, + Depth = depth }; var files = finder.GetScriptFiles(fileExtensions); if (files.Length == 0) { - AnsiConsole.Markup($"[red]No scripts script files found in {directory} with extensions '{string.Join("|", fileExtensions)}'[/]"); - Environment.Exit(1); + AnsiConsole.Markup($"[red]No scripts script files found in '{directory}' with extensions '{string.Join("|", fileExtensions)}'[/]"); + Environment.ExitCode = ErrorExitCode; + return; } - var prompt = new SelectionPrompt() + if (multiple) + { + var prompt = new MultiSelectionPrompt() + .Title("Select scripts") + .NotRequired() + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .AddChoices(files); + + var scripts = AnsiConsole.Prompt(prompt); + + scripts.ForEach(ScriptExecutor.Exec); + } + else + { + var prompt = new SelectionPrompt() .Title("Select a script") - .AddChoices(files.Select(x => x.FullName)); + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .AddChoices(files); - var script = AnsiConsole.Prompt(prompt); + var script = AnsiConsole.Prompt(prompt); - ScriptExecutor.Exec(script); + ScriptExecutor.Exec(script); + } }; static class ScriptExecutor { - internal static void Exec(string filename) + internal static void Exec(FileInfo file) { var info = new ProcessStartInfo { FileName = "powershell.exe", - Arguments = $"-File {filename}", + Arguments = $"-File {file.FullName}", UseShellExecute = false, }; @@ -64,11 +89,11 @@ static class ScriptExecutor internal static void Exec(ProcessStartInfo info) => Process.Start(info)?.WaitForExit(); } -class ScriptFinder +readonly struct ScriptFinder { - public string RootFolder { get; set; } = "."; - public string[] Extensions { get; set; } = new[] { "ps1", "*sh", "bat", "cmd" }; - public int Depth { get; set; } = 0; + public string RootFolder { get; init; } = "."; + public string[] Extensions { get; init; } = new[] { "ps1", "*sh", "bat", "cmd" }; + public int Depth { get; init; } = 0; private readonly EnumerationOptions _options; @@ -82,7 +107,7 @@ class ScriptFinder }; } - internal FileInfo[] GetScriptFiles(string extension) + internal readonly FileInfo[] GetScriptFiles(string extension) { try { @@ -95,6 +120,6 @@ class ScriptFinder } } - internal FileInfo[] GetScriptFiles(string[] extensions) => - extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); + internal readonly FileInfo[] GetScriptFiles() => + Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); } \ No newline at end of file From 1acf91adf5dde75e29282874a3060446a75a015b Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 16:39:40 +0100 Subject: [PATCH 005/100] Allow execution of various script types --- src/Program.cs | 63 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index d597268..aae8e0c 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -35,7 +35,7 @@ static void RootCommand( Depth = depth }; - var files = finder.GetScriptFiles(fileExtensions); + var files = finder.GetScriptFiles(); if (files.Length == 0) { @@ -56,7 +56,7 @@ static void RootCommand( var scripts = AnsiConsole.Prompt(prompt); - scripts.ForEach(ScriptExecutor.Exec); + scripts.ForEach(x => ScriptExecutor.Exec(x, elevated)); } else { @@ -68,25 +68,64 @@ static void RootCommand( var script = AnsiConsole.Prompt(prompt); - ScriptExecutor.Exec(script); + ScriptExecutor.Exec(script, elevated); } }; static class ScriptExecutor { - internal static void Exec(FileInfo file) + internal static void Exec(FileInfo file, bool elevated) { - var info = new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-File {file.FullName}", - UseShellExecute = false, - }; + var process = GetExecutableProcess(file, elevated); - Exec(info); + if (process is null) return; + + // todo: handle exceptions (shell not found) + Process.Start(process)?.WaitForExit(); } - internal static void Exec(ProcessStartInfo info) => Process.Start(info)?.WaitForExit(); + private static ProcessStartInfo? GetExecutableProcess(FileInfo file, bool elevated) + { + return file.Extension switch + { + ".bat" or ".cmd" => new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" => new ProcessStartInfo + { + FileName = "bash", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".zsh" => new ProcessStartInfo + { + FileName = "zsh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".fish" => new ProcessStartInfo + { + FileName = "fish", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + _ => null + }; + } } readonly struct ScriptFinder From 723677a72d7f39a45d5d125b4816fc2dfb813c3a Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 16:39:40 +0100 Subject: [PATCH 006/100] Allow execution of various script types --- src/Program.cs | 63 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index d597268..aae8e0c 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -35,7 +35,7 @@ static void RootCommand( Depth = depth }; - var files = finder.GetScriptFiles(fileExtensions); + var files = finder.GetScriptFiles(); if (files.Length == 0) { @@ -56,7 +56,7 @@ static void RootCommand( var scripts = AnsiConsole.Prompt(prompt); - scripts.ForEach(ScriptExecutor.Exec); + scripts.ForEach(x => ScriptExecutor.Exec(x, elevated)); } else { @@ -68,25 +68,64 @@ static void RootCommand( var script = AnsiConsole.Prompt(prompt); - ScriptExecutor.Exec(script); + ScriptExecutor.Exec(script, elevated); } }; static class ScriptExecutor { - internal static void Exec(FileInfo file) + internal static void Exec(FileInfo file, bool elevated) { - var info = new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-File {file.FullName}", - UseShellExecute = false, - }; + var process = GetExecutableProcess(file, elevated); - Exec(info); + if (process is null) return; + + // todo: handle exceptions (shell not found) + Process.Start(process)?.WaitForExit(); } - internal static void Exec(ProcessStartInfo info) => Process.Start(info)?.WaitForExit(); + private static ProcessStartInfo? GetExecutableProcess(FileInfo file, bool elevated) + { + return file.Extension switch + { + ".bat" or ".cmd" => new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" => new ProcessStartInfo + { + FileName = "bash", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".zsh" => new ProcessStartInfo + { + FileName = "zsh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".fish" => new ProcessStartInfo + { + FileName = "fish", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + _ => null + }; + } } readonly struct ScriptFinder From 2e52e63c3f7f4fe8d692dd078de0002e79d6e6e1 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 17:30:33 +0100 Subject: [PATCH 007/100] Add style to selection prompt --- src/Program.cs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index aae8e0c..f9faa55 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -47,11 +47,13 @@ static void RootCommand( if (multiple) { var prompt = new MultiSelectionPrompt() - .Title("Select scripts") + .Title("Select a script the scripts to execute:") .NotRequired() .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .UseConverter(PromptDecorator.GetStyledOption) + .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); var scripts = AnsiConsole.Prompt(prompt); @@ -61,9 +63,11 @@ static void RootCommand( else { var prompt = new SelectionPrompt() - .Title("Select a script") + .Title("Select a script to execute:") .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(PromptDecorator.GetStyledOption) + .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); var script = AnsiConsole.Prompt(prompt); @@ -72,6 +76,20 @@ static void RootCommand( } }; +static class PromptDecorator +{ + internal static string GetStyledOption(FileInfo info) + { + var directory = $"[blue]{info.DirectoryName ?? "."}{Path.DirectorySeparatorChar}[/]"; + var filename = $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]"; + var extension = $"[greenyellow]{Path.GetExtension(info.Name)}[/]"; + + return $"{directory}{filename}{extension}"; + } + + internal static Style SelectionHighlight => new Style(decoration: Decoration.Bold | Decoration.Underline); +} + static class ScriptExecutor { internal static void Exec(FileInfo file, bool elevated) @@ -146,19 +164,19 @@ readonly struct ScriptFinder }; } - internal readonly FileInfo[] GetScriptFiles(string extension) + internal readonly IEnumerable GetScriptFiles(string extension) { try { var filenames = Directory.GetFiles(RootFolder, $"*.{extension}", _options); - return filenames.Select(x => new FileInfo(x)).ToArray(); + return filenames.Select(x => new FileInfo(x)); } catch (UnauthorizedAccessException) { - return Array.Empty(); + return Enumerable.Empty(); } } - internal readonly FileInfo[] GetScriptFiles() => + internal readonly FileInfo[] GetScriptFiles() => Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); } \ No newline at end of file From 7c512fa63f7c19971b6651523c4b53dbe40578eb Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 17:30:33 +0100 Subject: [PATCH 008/100] Add style to selection prompt --- src/Program.cs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index aae8e0c..f9faa55 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -47,11 +47,13 @@ static void RootCommand( if (multiple) { var prompt = new MultiSelectionPrompt() - .Title("Select scripts") + .Title("Select a script the scripts to execute:") .NotRequired() .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .UseConverter(PromptDecorator.GetStyledOption) + .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); var scripts = AnsiConsole.Prompt(prompt); @@ -61,9 +63,11 @@ static void RootCommand( else { var prompt = new SelectionPrompt() - .Title("Select a script") + .Title("Select a script to execute:") .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(PromptDecorator.GetStyledOption) + .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); var script = AnsiConsole.Prompt(prompt); @@ -72,6 +76,20 @@ static void RootCommand( } }; +static class PromptDecorator +{ + internal static string GetStyledOption(FileInfo info) + { + var directory = $"[blue]{info.DirectoryName ?? "."}{Path.DirectorySeparatorChar}[/]"; + var filename = $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]"; + var extension = $"[greenyellow]{Path.GetExtension(info.Name)}[/]"; + + return $"{directory}{filename}{extension}"; + } + + internal static Style SelectionHighlight => new Style(decoration: Decoration.Bold | Decoration.Underline); +} + static class ScriptExecutor { internal static void Exec(FileInfo file, bool elevated) @@ -146,19 +164,19 @@ readonly struct ScriptFinder }; } - internal readonly FileInfo[] GetScriptFiles(string extension) + internal readonly IEnumerable GetScriptFiles(string extension) { try { var filenames = Directory.GetFiles(RootFolder, $"*.{extension}", _options); - return filenames.Select(x => new FileInfo(x)).ToArray(); + return filenames.Select(x => new FileInfo(x)); } catch (UnauthorizedAccessException) { - return Array.Empty(); + return Enumerable.Empty(); } } - internal readonly FileInfo[] GetScriptFiles() => + internal readonly FileInfo[] GetScriptFiles() => Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); } \ No newline at end of file From 8f9fa240b64a8362757dc54d6365f68489c698fc Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 18:04:07 +0100 Subject: [PATCH 009/100] Make script execution asyncronous --- src/Program.cs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index f9faa55..6a7220f 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -10,7 +10,7 @@ var app = CoconaLiteApp.Create(); app.AddCommand(RootCommand); app.Run(); -static void RootCommand( +static async Task RootCommand( [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, @@ -58,7 +58,7 @@ static void RootCommand( var scripts = AnsiConsole.Prompt(prompt); - scripts.ForEach(x => ScriptExecutor.Exec(x, elevated)); + await ScriptExecutor.ExecAsync(scripts, elevated); } else { @@ -72,7 +72,7 @@ static void RootCommand( var script = AnsiConsole.Prompt(prompt); - ScriptExecutor.Exec(script, elevated); + await ScriptExecutor.ExecAsync(script, elevated); } }; @@ -92,17 +92,21 @@ static class PromptDecorator static class ScriptExecutor { - internal static void Exec(FileInfo file, bool elevated) + internal static async Task ExecAsync(List files, bool elevated) { - var process = GetExecutableProcess(file, elevated); + await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); + } + + internal static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + { + var process = GetExecutableProcessInfo(file, elevated); if (process is null) return; - // todo: handle exceptions (shell not found) - Process.Start(process)?.WaitForExit(); + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); } - private static ProcessStartInfo? GetExecutableProcess(FileInfo file, bool elevated) + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) { return file.Extension switch { @@ -154,15 +158,12 @@ readonly struct ScriptFinder private readonly EnumerationOptions _options; - public ScriptFinder() + public ScriptFinder() => _options = new EnumerationOptions { - _options = new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = Depth > 0, - MaxRecursionDepth = Depth, - }; - } + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; internal readonly IEnumerable GetScriptFiles(string extension) { From 4c53f7dc06595ed810394cb4aa7c7cdcd302bf5a Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 11 Mar 2022 18:04:07 +0100 Subject: [PATCH 010/100] Make script execution asyncronous --- src/Program.cs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index f9faa55..6a7220f 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -10,7 +10,7 @@ var app = CoconaLiteApp.Create(); app.AddCommand(RootCommand); app.Run(); -static void RootCommand( +static async Task RootCommand( [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, @@ -58,7 +58,7 @@ static void RootCommand( var scripts = AnsiConsole.Prompt(prompt); - scripts.ForEach(x => ScriptExecutor.Exec(x, elevated)); + await ScriptExecutor.ExecAsync(scripts, elevated); } else { @@ -72,7 +72,7 @@ static void RootCommand( var script = AnsiConsole.Prompt(prompt); - ScriptExecutor.Exec(script, elevated); + await ScriptExecutor.ExecAsync(script, elevated); } }; @@ -92,17 +92,21 @@ static class PromptDecorator static class ScriptExecutor { - internal static void Exec(FileInfo file, bool elevated) + internal static async Task ExecAsync(List files, bool elevated) { - var process = GetExecutableProcess(file, elevated); + await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); + } + + internal static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + { + var process = GetExecutableProcessInfo(file, elevated); if (process is null) return; - // todo: handle exceptions (shell not found) - Process.Start(process)?.WaitForExit(); + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); } - private static ProcessStartInfo? GetExecutableProcess(FileInfo file, bool elevated) + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) { return file.Extension switch { @@ -154,15 +158,12 @@ readonly struct ScriptFinder private readonly EnumerationOptions _options; - public ScriptFinder() + public ScriptFinder() => _options = new EnumerationOptions { - _options = new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = Depth > 0, - MaxRecursionDepth = Depth, - }; - } + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; internal readonly IEnumerable GetScriptFiles(string extension) { From f8df61cad6e5dc3d989b2629a9914daa6f18e12c Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 12 Mar 2022 10:46:20 +0100 Subject: [PATCH 011/100] Add exception handling on script exec faliure --- src/Program.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 6a7220f..ae1d36e 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; using Cocona; using Spectre.Console; @@ -14,7 +15,7 @@ static async Task RootCommand( [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts")] bool multiple = false, + [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) @@ -74,7 +75,7 @@ static async Task RootCommand( await ScriptExecutor.ExecAsync(script, elevated); } -}; +} static class PromptDecorator { @@ -103,7 +104,14 @@ static class ScriptExecutor if (process is null) return; - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + try + { + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + } + catch (Exception ex) when (ex is Win32Exception || ex is InvalidOperationException || ex is PlatformNotSupportedException) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + } } private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) @@ -165,7 +173,7 @@ readonly struct ScriptFinder MaxRecursionDepth = Depth, }; - internal readonly IEnumerable GetScriptFiles(string extension) + private IEnumerable GetScriptFiles(string extension) { try { @@ -178,6 +186,6 @@ readonly struct ScriptFinder } } - internal readonly FileInfo[] GetScriptFiles() => + internal FileInfo[] GetScriptFiles() => Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); } \ No newline at end of file From cc8f4fedb4d229fb55ca03b33ae60998afd4d5d6 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 12 Mar 2022 10:46:20 +0100 Subject: [PATCH 012/100] Add exception handling on script exec faliure --- src/Program.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 6a7220f..ae1d36e 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; using Cocona; using Spectre.Console; @@ -14,7 +15,7 @@ static async Task RootCommand( [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts")] bool multiple = false, + [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) @@ -74,7 +75,7 @@ static async Task RootCommand( await ScriptExecutor.ExecAsync(script, elevated); } -}; +} static class PromptDecorator { @@ -103,7 +104,14 @@ static class ScriptExecutor if (process is null) return; - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + try + { + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + } + catch (Exception ex) when (ex is Win32Exception || ex is InvalidOperationException || ex is PlatformNotSupportedException) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + } } private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) @@ -165,7 +173,7 @@ readonly struct ScriptFinder MaxRecursionDepth = Depth, }; - internal readonly IEnumerable GetScriptFiles(string extension) + private IEnumerable GetScriptFiles(string extension) { try { @@ -178,6 +186,6 @@ readonly struct ScriptFinder } } - internal readonly FileInfo[] GetScriptFiles() => + internal FileInfo[] GetScriptFiles() => Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); } \ No newline at end of file From ad2c894f15af22bc99c705ace0d28f67e27bf81a Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 12 Mar 2022 12:23:18 +0100 Subject: [PATCH 013/100] Fix default extensions fallback when not specified --- src/Program.cs | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index ae1d36e..ef3adc8 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -5,6 +5,7 @@ using Spectre.Console; const int ScriptListSize = 15; const int ErrorExitCode = 1; +string[] DefaultExtensions = new[] { "ps1", "*sh", "bat", "cmd" }; var app = CoconaLiteApp.Create(); @@ -12,7 +13,7 @@ app.AddCommand(RootCommand); app.Run(); static async Task RootCommand( - [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", + [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, @@ -25,22 +26,12 @@ static async Task RootCommand( return; } - var fileExtensions = extensions.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .ToHashSet() - .ToArray(); - - var finder = new ScriptFinder - { - Extensions = fileExtensions, - RootFolder = directory, - Depth = depth - }; - + var finder = new ScriptFinder(extensions, directory, depth); var files = finder.GetScriptFiles(); if (files.Length == 0) { - AnsiConsole.Markup($"[red]No scripts script files found in '{directory}' with extensions '{string.Join("|", fileExtensions)}'[/]"); + AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); Environment.ExitCode = ErrorExitCode; return; } @@ -88,7 +79,7 @@ static class PromptDecorator return $"{directory}{filename}{extension}"; } - internal static Style SelectionHighlight => new Style(decoration: Decoration.Bold | Decoration.Underline); + internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); } static class ScriptExecutor @@ -160,24 +151,34 @@ static class ScriptExecutor readonly struct ScriptFinder { - public string RootFolder { get; init; } = "."; - public string[] Extensions { get; init; } = new[] { "ps1", "*sh", "bat", "cmd" }; - public int Depth { get; init; } = 0; - + public string[] Extensions { get; } + public string RootDirectory { get; } + public int Depth { get; } private readonly EnumerationOptions _options; - public ScriptFinder() => _options = new EnumerationOptions + public ScriptFinder(string? extensions, string directory, int depth) { - IgnoreInaccessible = true, - RecurseSubdirectories = Depth > 0, - MaxRecursionDepth = Depth, - }; + Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .Select(x => $".{x.TrimStart('.')}") + .ToArray() ?? new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + + Depth = depth; + RootDirectory = directory; + + _options = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; + } private IEnumerable GetScriptFiles(string extension) { try { - var filenames = Directory.GetFiles(RootFolder, $"*.{extension}", _options); + var filenames = Directory.GetFiles(RootDirectory, $"*{extension}", _options); return filenames.Select(x => new FileInfo(x)); } catch (UnauthorizedAccessException) From b80a1c7c834fb1df1d3efdaa8a1c5f358ddbecee Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 12 Mar 2022 12:23:18 +0100 Subject: [PATCH 014/100] Fix default extensions fallback when not specified --- src/Program.cs | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index ae1d36e..ef3adc8 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -5,6 +5,7 @@ using Spectre.Console; const int ScriptListSize = 15; const int ErrorExitCode = 1; +string[] DefaultExtensions = new[] { "ps1", "*sh", "bat", "cmd" }; var app = CoconaLiteApp.Create(); @@ -12,7 +13,7 @@ app.AddCommand(RootCommand); app.Run(); static async Task RootCommand( - [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string extensions = "*", + [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, @@ -25,22 +26,12 @@ static async Task RootCommand( return; } - var fileExtensions = extensions.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .ToHashSet() - .ToArray(); - - var finder = new ScriptFinder - { - Extensions = fileExtensions, - RootFolder = directory, - Depth = depth - }; - + var finder = new ScriptFinder(extensions, directory, depth); var files = finder.GetScriptFiles(); if (files.Length == 0) { - AnsiConsole.Markup($"[red]No scripts script files found in '{directory}' with extensions '{string.Join("|", fileExtensions)}'[/]"); + AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); Environment.ExitCode = ErrorExitCode; return; } @@ -88,7 +79,7 @@ static class PromptDecorator return $"{directory}{filename}{extension}"; } - internal static Style SelectionHighlight => new Style(decoration: Decoration.Bold | Decoration.Underline); + internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); } static class ScriptExecutor @@ -160,24 +151,34 @@ static class ScriptExecutor readonly struct ScriptFinder { - public string RootFolder { get; init; } = "."; - public string[] Extensions { get; init; } = new[] { "ps1", "*sh", "bat", "cmd" }; - public int Depth { get; init; } = 0; - + public string[] Extensions { get; } + public string RootDirectory { get; } + public int Depth { get; } private readonly EnumerationOptions _options; - public ScriptFinder() => _options = new EnumerationOptions + public ScriptFinder(string? extensions, string directory, int depth) { - IgnoreInaccessible = true, - RecurseSubdirectories = Depth > 0, - MaxRecursionDepth = Depth, - }; + Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .Select(x => $".{x.TrimStart('.')}") + .ToArray() ?? new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + + Depth = depth; + RootDirectory = directory; + + _options = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; + } private IEnumerable GetScriptFiles(string extension) { try { - var filenames = Directory.GetFiles(RootFolder, $"*.{extension}", _options); + var filenames = Directory.GetFiles(RootDirectory, $"*{extension}", _options); return filenames.Select(x => new FileInfo(x)); } catch (UnauthorizedAccessException) From 151ee1d6a0df1438d4caaaf359bd729c6ecee00c Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 14 Mar 2022 12:18:16 +0100 Subject: [PATCH 015/100] Add publish script --- .gitignore | 3 +++ publish-win.ps1 | 1 + 2 files changed, 4 insertions(+) create mode 100644 publish-win.ps1 diff --git a/.gitignore b/.gitignore index 6ca3ba7..f78b2cc 100644 --- a/.gitignore +++ b/.gitignore @@ -448,3 +448,6 @@ $RECYCLE.BIN/ ## Visual Studio Code ## .vscode/ + +# output directory +out/ \ No newline at end of file diff --git a/publish-win.ps1 b/publish-win.ps1 new file mode 100644 index 0000000..5fbf40c --- /dev/null +++ b/publish-win.ps1 @@ -0,0 +1 @@ +dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r win-x64 .\src\ \ No newline at end of file From 15475143e344d5db8125900a5069ed6b62c694c5 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 14 Mar 2022 12:18:16 +0100 Subject: [PATCH 016/100] Add publish script --- .gitignore | 3 +++ publish-win.ps1 | 1 + 2 files changed, 4 insertions(+) create mode 100644 publish-win.ps1 diff --git a/.gitignore b/.gitignore index 6ca3ba7..f78b2cc 100644 --- a/.gitignore +++ b/.gitignore @@ -448,3 +448,6 @@ $RECYCLE.BIN/ ## Visual Studio Code ## .vscode/ + +# output directory +out/ \ No newline at end of file diff --git a/publish-win.ps1 b/publish-win.ps1 new file mode 100644 index 0000000..5fbf40c --- /dev/null +++ b/publish-win.ps1 @@ -0,0 +1 @@ +dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r win-x64 .\src\ \ No newline at end of file From c35a51d9a7dfc6da7fd7c999217c62c2a782222f Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 14 Mar 2022 19:23:24 +0100 Subject: [PATCH 017/100] Show only relative script path in prompt --- src/Program.cs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index ef3adc8..81c9087 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Diagnostics; +using System.Text; using Cocona; using Spectre.Console; @@ -44,7 +45,7 @@ static async Task RootCommand( .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .UseConverter(PromptDecorator.GetStyledOption) + .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -58,7 +59,7 @@ static async Task RootCommand( .Title("Select a script to execute:") .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(PromptDecorator.GetStyledOption) + .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -70,13 +71,24 @@ static async Task RootCommand( static class PromptDecorator { - internal static string GetStyledOption(FileInfo info) + internal static string GetStyledOption(FileInfo info, string root) { - var directory = $"[blue]{info.DirectoryName ?? "."}{Path.DirectorySeparatorChar}[/]"; - var filename = $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]"; - var extension = $"[greenyellow]{Path.GetExtension(info.Name)}[/]"; + var builder = new StringBuilder(); - return $"{directory}{filename}{extension}"; + var directory = Path.GetRelativePath(root, info.DirectoryName ?? ".").TrimStart('.'); + var filename = Path.GetFileNameWithoutExtension(info.Name); + var extension = Path.GetExtension(info.Name); + + builder.Append($"[blue].{Path.DirectorySeparatorChar}[/]"); + + if(!string.IsNullOrWhiteSpace(directory)) + { + builder.Append($"[blue]{directory}{Path.DirectorySeparatorChar}[/]"); + } + + builder.Append($"[orangered1]{filename}[/][greenyellow]{extension}[/]"); + + return builder.ToString(); } internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); @@ -99,7 +111,7 @@ static class ScriptExecutor { await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); } - catch (Exception ex) when (ex is Win32Exception || ex is InvalidOperationException || ex is PlatformNotSupportedException) + catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) { AnsiConsole.Markup($"[red]{ex.Message}[/]"); } @@ -189,4 +201,4 @@ readonly struct ScriptFinder internal FileInfo[] GetScriptFiles() => Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); -} \ No newline at end of file +} From a8cb72df0aee97d9218c237a442ff8e987812c18 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 14 Mar 2022 19:23:24 +0100 Subject: [PATCH 018/100] Show only relative script path in prompt --- src/Program.cs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index ef3adc8..81c9087 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Diagnostics; +using System.Text; using Cocona; using Spectre.Console; @@ -44,7 +45,7 @@ static async Task RootCommand( .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .UseConverter(PromptDecorator.GetStyledOption) + .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -58,7 +59,7 @@ static async Task RootCommand( .Title("Select a script to execute:") .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(PromptDecorator.GetStyledOption) + .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -70,13 +71,24 @@ static async Task RootCommand( static class PromptDecorator { - internal static string GetStyledOption(FileInfo info) + internal static string GetStyledOption(FileInfo info, string root) { - var directory = $"[blue]{info.DirectoryName ?? "."}{Path.DirectorySeparatorChar}[/]"; - var filename = $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]"; - var extension = $"[greenyellow]{Path.GetExtension(info.Name)}[/]"; + var builder = new StringBuilder(); - return $"{directory}{filename}{extension}"; + var directory = Path.GetRelativePath(root, info.DirectoryName ?? ".").TrimStart('.'); + var filename = Path.GetFileNameWithoutExtension(info.Name); + var extension = Path.GetExtension(info.Name); + + builder.Append($"[blue].{Path.DirectorySeparatorChar}[/]"); + + if(!string.IsNullOrWhiteSpace(directory)) + { + builder.Append($"[blue]{directory}{Path.DirectorySeparatorChar}[/]"); + } + + builder.Append($"[orangered1]{filename}[/][greenyellow]{extension}[/]"); + + return builder.ToString(); } internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); @@ -99,7 +111,7 @@ static class ScriptExecutor { await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); } - catch (Exception ex) when (ex is Win32Exception || ex is InvalidOperationException || ex is PlatformNotSupportedException) + catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) { AnsiConsole.Markup($"[red]{ex.Message}[/]"); } @@ -189,4 +201,4 @@ readonly struct ScriptFinder internal FileInfo[] GetScriptFiles() => Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); -} \ No newline at end of file +} From 555b65836a2c04578b37440d867cc692690051f2 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 14 Mar 2022 20:45:01 +0100 Subject: [PATCH 019/100] Allow selection grouped by directory --- src/Program.cs | 60 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 81c9087..14ce1d4 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -15,9 +15,10 @@ app.Run(); static async Task RootCommand( [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, - [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, + [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, + [Option("grouped", new char[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) @@ -27,8 +28,27 @@ static async Task RootCommand( return; } + var files = Array.Empty(); var finder = new ScriptFinder(extensions, directory, depth); - var files = finder.GetScriptFiles(); + + if (grouped) + { + var dict = finder.GetScriptsByDirectory(); + var prompt = new SelectionPrompt() + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(x => $"[blue]{x}[/]") + .HighlightStyle(PromptDecorator.SelectionHighlight) + .AddChoices(dict.Keys); + + var directoryInfo = AnsiConsole.Prompt(prompt); + files = dict[directoryInfo]; + } + else + { + files = finder.GetScripts(); + } if (files.Length == 0) { @@ -45,7 +65,7 @@ static async Task RootCommand( .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) + .UseConverter(PromptDecorator.FileStyle) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -59,7 +79,7 @@ static async Task RootCommand( .Title("Select a script to execute:") .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) + .UseConverter(PromptDecorator.FileStyle) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -71,26 +91,16 @@ static async Task RootCommand( static class PromptDecorator { - internal static string GetStyledOption(FileInfo info, string root) + internal static string FileStyle(FileInfo info) { - var builder = new StringBuilder(); - - var directory = Path.GetRelativePath(root, info.DirectoryName ?? ".").TrimStart('.'); var filename = Path.GetFileNameWithoutExtension(info.Name); var extension = Path.GetExtension(info.Name); - builder.Append($"[blue].{Path.DirectorySeparatorChar}[/]"); - - if(!string.IsNullOrWhiteSpace(directory)) - { - builder.Append($"[blue]{directory}{Path.DirectorySeparatorChar}[/]"); - } - - builder.Append($"[orangered1]{filename}[/][greenyellow]{extension}[/]"); - - return builder.ToString(); + return $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/][orangered1]{filename}[/][greenyellow]{extension}[/]"; } + internal static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); } @@ -186,7 +196,7 @@ readonly struct ScriptFinder }; } - private IEnumerable GetScriptFiles(string extension) + private IEnumerable GetScriptFilesWithExtension(string extension) { try { @@ -199,6 +209,14 @@ readonly struct ScriptFinder } } - internal FileInfo[] GetScriptFiles() => - Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); + internal FileInfo[] GetScripts() => + Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); + + internal IDictionary GetScriptsByDirectory() => + Extensions + .Select(GetScriptFilesWithExtension) + .SelectMany(x => x) + .GroupBy(x => x.DirectoryName!) + .OrderBy(x => x.Key) + .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); } From c5b31e06bc87134b70cb53c5612e165870c1fa7e Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 14 Mar 2022 20:45:01 +0100 Subject: [PATCH 020/100] Allow selection grouped by directory --- src/Program.cs | 60 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 81c9087..14ce1d4 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -15,9 +15,10 @@ app.Run(); static async Task RootCommand( [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, - [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 0, + [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, + [Option("grouped", new char[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) @@ -27,8 +28,27 @@ static async Task RootCommand( return; } + var files = Array.Empty(); var finder = new ScriptFinder(extensions, directory, depth); - var files = finder.GetScriptFiles(); + + if (grouped) + { + var dict = finder.GetScriptsByDirectory(); + var prompt = new SelectionPrompt() + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(x => $"[blue]{x}[/]") + .HighlightStyle(PromptDecorator.SelectionHighlight) + .AddChoices(dict.Keys); + + var directoryInfo = AnsiConsole.Prompt(prompt); + files = dict[directoryInfo]; + } + else + { + files = finder.GetScripts(); + } if (files.Length == 0) { @@ -45,7 +65,7 @@ static async Task RootCommand( .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) + .UseConverter(PromptDecorator.FileStyle) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -59,7 +79,7 @@ static async Task RootCommand( .Title("Select a script to execute:") .PageSize(ScriptListSize) .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => PromptDecorator.GetStyledOption(x, directory)) + .UseConverter(PromptDecorator.FileStyle) .HighlightStyle(PromptDecorator.SelectionHighlight) .AddChoices(files); @@ -71,26 +91,16 @@ static async Task RootCommand( static class PromptDecorator { - internal static string GetStyledOption(FileInfo info, string root) + internal static string FileStyle(FileInfo info) { - var builder = new StringBuilder(); - - var directory = Path.GetRelativePath(root, info.DirectoryName ?? ".").TrimStart('.'); var filename = Path.GetFileNameWithoutExtension(info.Name); var extension = Path.GetExtension(info.Name); - builder.Append($"[blue].{Path.DirectorySeparatorChar}[/]"); - - if(!string.IsNullOrWhiteSpace(directory)) - { - builder.Append($"[blue]{directory}{Path.DirectorySeparatorChar}[/]"); - } - - builder.Append($"[orangered1]{filename}[/][greenyellow]{extension}[/]"); - - return builder.ToString(); + return $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/][orangered1]{filename}[/][greenyellow]{extension}[/]"; } + internal static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); } @@ -186,7 +196,7 @@ readonly struct ScriptFinder }; } - private IEnumerable GetScriptFiles(string extension) + private IEnumerable GetScriptFilesWithExtension(string extension) { try { @@ -199,6 +209,14 @@ readonly struct ScriptFinder } } - internal FileInfo[] GetScriptFiles() => - Extensions.Select(GetScriptFiles).SelectMany(x => x).ToArray(); + internal FileInfo[] GetScripts() => + Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); + + internal IDictionary GetScriptsByDirectory() => + Extensions + .Select(GetScriptFilesWithExtension) + .SelectMany(x => x) + .GroupBy(x => x.DirectoryName!) + .OrderBy(x => x.Key) + .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); } From bd4683b1104b9db5c24b7a83e56b9729ae1fd044 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 16 Mar 2022 09:54:01 +0100 Subject: [PATCH 021/100] Add error message if no files are found in grouped mode --- src/Program.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Program.cs b/src/Program.cs index 14ce1d4..b70e239 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Diagnostics; -using System.Text; using Cocona; using Spectre.Console; @@ -34,6 +33,14 @@ static async Task RootCommand( if (grouped) { var dict = finder.GetScriptsByDirectory(); + + if (dict.Count == 0) + { + AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); + Environment.ExitCode = ErrorExitCode; + return; + } + var prompt = new SelectionPrompt() .Title("Select a directory:") .PageSize(ScriptListSize) From 05e76965fd41b558e6fbb7aa9d0321cfe280057a Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 16 Mar 2022 09:54:01 +0100 Subject: [PATCH 022/100] Add error message if no files are found in grouped mode --- src/Program.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Program.cs b/src/Program.cs index 14ce1d4..b70e239 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Diagnostics; -using System.Text; using Cocona; using Spectre.Console; @@ -34,6 +33,14 @@ static async Task RootCommand( if (grouped) { var dict = finder.GetScriptsByDirectory(); + + if (dict.Count == 0) + { + AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); + Environment.ExitCode = ErrorExitCode; + return; + } + var prompt = new SelectionPrompt() .Title("Select a directory:") .PageSize(ScriptListSize) From d3ab7aaa3104b3dd3f6c97dcf677f59ca993b2f3 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 16 Mar 2022 12:27:33 +0100 Subject: [PATCH 023/100] Move prompt handling in a common class --- src/Program.cs | 117 ++++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index b70e239..bcec708 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -3,31 +3,27 @@ using System.Diagnostics; using Cocona; using Spectre.Console; -const int ScriptListSize = 15; -const int ErrorExitCode = 1; -string[] DefaultExtensions = new[] { "ps1", "*sh", "bat", "cmd" }; - var app = CoconaLiteApp.Create(); app.AddCommand(RootCommand); app.Run(); static async Task RootCommand( - [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, - [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, - [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, - [Option("grouped", new char[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, + [Option("extensions", new[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, + [Option("depth", new[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, + [Option("elevated", new[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, + [Option("multiple", new[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, + [Option("grouped", new[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) { AnsiConsole.Markup($"[red]The directory '{directory}' does not exist.[/]"); - Environment.ExitCode = ErrorExitCode; + Environment.ExitCode = 1; return; } - var files = Array.Empty(); + FileInfo[] files; var finder = new ScriptFinder(extensions, directory, depth); if (grouped) @@ -37,18 +33,11 @@ static async Task RootCommand( if (dict.Count == 0) { AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = ErrorExitCode; + Environment.ExitCode = 1; return; } - var prompt = new SelectionPrompt() - .Title("Select a directory:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => $"[blue]{x}[/]") - .HighlightStyle(PromptDecorator.SelectionHighlight) - .AddChoices(dict.Keys); - + var prompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); var directoryInfo = AnsiConsole.Prompt(prompt); files = dict[directoryInfo]; } @@ -60,35 +49,20 @@ static async Task RootCommand( if (files.Length == 0) { AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = ErrorExitCode; + Environment.ExitCode = 1; return; } if (multiple) { - var prompt = new MultiSelectionPrompt() - .Title("Select a script the scripts to execute:") - .NotRequired() - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .UseConverter(PromptDecorator.FileStyle) - .HighlightStyle(PromptDecorator.SelectionHighlight) - .AddChoices(files); - + var prompt = PromptConstructor.GetMultiSelectionPrompt(files); var scripts = AnsiConsole.Prompt(prompt); await ScriptExecutor.ExecAsync(scripts, elevated); } else { - var prompt = new SelectionPrompt() - .Title("Select a script to execute:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(PromptDecorator.FileStyle) - .HighlightStyle(PromptDecorator.SelectionHighlight) - .AddChoices(files); + var prompt = PromptConstructor.GetSelectionPrompt(files); var script = AnsiConsole.Prompt(prompt); @@ -96,29 +70,69 @@ static async Task RootCommand( } } -static class PromptDecorator +static class PromptConstructor { - internal static string FileStyle(FileInfo info) - { - var filename = Path.GetFileNameWithoutExtension(info.Name); - var extension = Path.GetExtension(info.Name); + const int ScriptListSize = 15; - return $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/][orangered1]{filename}[/][greenyellow]{extension}[/]"; + private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + + private static string FileStyle(FileInfo info) => + $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]" + + $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]" + + $"[greenyellow]{info.Extension}[/]"; + + private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + + public static SelectionPrompt GetSelectionPrompt(FileInfo[] files) + { + var prompt = new SelectionPrompt() + .Title("Select a script to execute:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(FileStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); + + return prompt; } - internal static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + public static MultiSelectionPrompt GetMultiSelectionPrompt(FileInfo[] files) + { + var prompt = new MultiSelectionPrompt() + .Title("Select a script the scripts to execute:") + .NotRequired() + .PageSize(ScriptListSize) + .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(FileStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); - internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + return prompt; + } + + public static SelectionPrompt GetSelectionPrompt(DirectoryInfo[] directories) + { + var prompt = new SelectionPrompt() + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(DirectoryStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(directories); + + return prompt; + } } static class ScriptExecutor { - internal static async Task ExecAsync(List files, bool elevated) + public static async Task ExecAsync(List files, bool elevated) { await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); } - internal static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) { var process = GetExecutableProcessInfo(file, elevated); @@ -180,6 +194,7 @@ static class ScriptExecutor readonly struct ScriptFinder { + static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; public string[] Extensions { get; } public string RootDirectory { get; } public int Depth { get; } @@ -190,7 +205,7 @@ readonly struct ScriptFinder Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) .ToHashSet() .Select(x => $".{x.TrimStart('.')}") - .ToArray() ?? new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + .ToArray() ?? DefaultExtensions; Depth = depth; RootDirectory = directory; @@ -216,10 +231,10 @@ readonly struct ScriptFinder } } - internal FileInfo[] GetScripts() => + public FileInfo[] GetScripts() => Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); - internal IDictionary GetScriptsByDirectory() => + public IDictionary GetScriptsByDirectory() => Extensions .Select(GetScriptFilesWithExtension) .SelectMany(x => x) From a3db4d5fc323240966437007ed6dd9ace5c810f1 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 16 Mar 2022 12:27:33 +0100 Subject: [PATCH 024/100] Move prompt handling in a common class --- src/Program.cs | 117 ++++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index b70e239..bcec708 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -3,31 +3,27 @@ using System.Diagnostics; using Cocona; using Spectre.Console; -const int ScriptListSize = 15; -const int ErrorExitCode = 1; -string[] DefaultExtensions = new[] { "ps1", "*sh", "bat", "cmd" }; - var app = CoconaLiteApp.Create(); app.AddCommand(RootCommand); app.Run(); static async Task RootCommand( - [Option("extensions", new char[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, - [Option("depth", new char[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, - [Option("elevated", new char[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("multiple", new char[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, - [Option("grouped", new char[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, + [Option("extensions", new[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, + [Option("depth", new[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, + [Option("elevated", new[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, + [Option("multiple", new[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, + [Option("grouped", new[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { if (!Directory.Exists(directory)) { AnsiConsole.Markup($"[red]The directory '{directory}' does not exist.[/]"); - Environment.ExitCode = ErrorExitCode; + Environment.ExitCode = 1; return; } - var files = Array.Empty(); + FileInfo[] files; var finder = new ScriptFinder(extensions, directory, depth); if (grouped) @@ -37,18 +33,11 @@ static async Task RootCommand( if (dict.Count == 0) { AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = ErrorExitCode; + Environment.ExitCode = 1; return; } - var prompt = new SelectionPrompt() - .Title("Select a directory:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => $"[blue]{x}[/]") - .HighlightStyle(PromptDecorator.SelectionHighlight) - .AddChoices(dict.Keys); - + var prompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); var directoryInfo = AnsiConsole.Prompt(prompt); files = dict[directoryInfo]; } @@ -60,35 +49,20 @@ static async Task RootCommand( if (files.Length == 0) { AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = ErrorExitCode; + Environment.ExitCode = 1; return; } if (multiple) { - var prompt = new MultiSelectionPrompt() - .Title("Select a script the scripts to execute:") - .NotRequired() - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .UseConverter(PromptDecorator.FileStyle) - .HighlightStyle(PromptDecorator.SelectionHighlight) - .AddChoices(files); - + var prompt = PromptConstructor.GetMultiSelectionPrompt(files); var scripts = AnsiConsole.Prompt(prompt); await ScriptExecutor.ExecAsync(scripts, elevated); } else { - var prompt = new SelectionPrompt() - .Title("Select a script to execute:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(PromptDecorator.FileStyle) - .HighlightStyle(PromptDecorator.SelectionHighlight) - .AddChoices(files); + var prompt = PromptConstructor.GetSelectionPrompt(files); var script = AnsiConsole.Prompt(prompt); @@ -96,29 +70,69 @@ static async Task RootCommand( } } -static class PromptDecorator +static class PromptConstructor { - internal static string FileStyle(FileInfo info) - { - var filename = Path.GetFileNameWithoutExtension(info.Name); - var extension = Path.GetExtension(info.Name); + const int ScriptListSize = 15; - return $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/][orangered1]{filename}[/][greenyellow]{extension}[/]"; + private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + + private static string FileStyle(FileInfo info) => + $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]" + + $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]" + + $"[greenyellow]{info.Extension}[/]"; + + private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + + public static SelectionPrompt GetSelectionPrompt(FileInfo[] files) + { + var prompt = new SelectionPrompt() + .Title("Select a script to execute:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(FileStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); + + return prompt; } - internal static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + public static MultiSelectionPrompt GetMultiSelectionPrompt(FileInfo[] files) + { + var prompt = new MultiSelectionPrompt() + .Title("Select a script the scripts to execute:") + .NotRequired() + .PageSize(ScriptListSize) + .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(FileStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); - internal static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + return prompt; + } + + public static SelectionPrompt GetSelectionPrompt(DirectoryInfo[] directories) + { + var prompt = new SelectionPrompt() + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(DirectoryStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(directories); + + return prompt; + } } static class ScriptExecutor { - internal static async Task ExecAsync(List files, bool elevated) + public static async Task ExecAsync(List files, bool elevated) { await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); } - internal static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) { var process = GetExecutableProcessInfo(file, elevated); @@ -180,6 +194,7 @@ static class ScriptExecutor readonly struct ScriptFinder { + static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; public string[] Extensions { get; } public string RootDirectory { get; } public int Depth { get; } @@ -190,7 +205,7 @@ readonly struct ScriptFinder Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) .ToHashSet() .Select(x => $".{x.TrimStart('.')}") - .ToArray() ?? new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + .ToArray() ?? DefaultExtensions; Depth = depth; RootDirectory = directory; @@ -216,10 +231,10 @@ readonly struct ScriptFinder } } - internal FileInfo[] GetScripts() => + public FileInfo[] GetScripts() => Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); - internal IDictionary GetScriptsByDirectory() => + public IDictionary GetScriptsByDirectory() => Extensions .Select(GetScriptFilesWithExtension) .SelectMany(x => x) From 1a6f72bf78f5f41ccc2815e981faf9583dcfe9d9 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 26 Apr 2022 21:54:10 +0200 Subject: [PATCH 025/100] Update publish script --- dotnet-publish.ps1 | 26 ++++++++++++++++++++++++++ publish-win.ps1 | 1 - 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 dotnet-publish.ps1 delete mode 100644 publish-win.ps1 diff --git a/dotnet-publish.ps1 b/dotnet-publish.ps1 new file mode 100644 index 0000000..43860c0 --- /dev/null +++ b/dotnet-publish.ps1 @@ -0,0 +1,26 @@ +function Get-Runtime +{ + if($isLinux) + { + return "linux-x64"; + } + + if($isMac) + { + return "osx-x64"; + } + + if($isWindows) + { + return "win-x64"; + } + + return $null; +} + +$runtime = Get-Runtime; + +if($runtime -ne $null) +{ + dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r $runtime .\src\ +} \ No newline at end of file diff --git a/publish-win.ps1 b/publish-win.ps1 deleted file mode 100644 index 5fbf40c..0000000 --- a/publish-win.ps1 +++ /dev/null @@ -1 +0,0 @@ -dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r win-x64 .\src\ \ No newline at end of file From 1df685119d866cb9e483c59b93d3159c9be60fbe Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 26 Apr 2022 21:54:10 +0200 Subject: [PATCH 026/100] Update publish script --- dotnet-publish.ps1 | 26 ++++++++++++++++++++++++++ publish-win.ps1 | 1 - 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 dotnet-publish.ps1 delete mode 100644 publish-win.ps1 diff --git a/dotnet-publish.ps1 b/dotnet-publish.ps1 new file mode 100644 index 0000000..43860c0 --- /dev/null +++ b/dotnet-publish.ps1 @@ -0,0 +1,26 @@ +function Get-Runtime +{ + if($isLinux) + { + return "linux-x64"; + } + + if($isMac) + { + return "osx-x64"; + } + + if($isWindows) + { + return "win-x64"; + } + + return $null; +} + +$runtime = Get-Runtime; + +if($runtime -ne $null) +{ + dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r $runtime .\src\ +} \ No newline at end of file diff --git a/publish-win.ps1 b/publish-win.ps1 deleted file mode 100644 index 5fbf40c..0000000 --- a/publish-win.ps1 +++ /dev/null @@ -1 +0,0 @@ -dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r win-x64 .\src\ \ No newline at end of file From dfd999dff4d05a31bb433472f27a5c79fd4d1da8 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 24 May 2022 18:46:04 +0200 Subject: [PATCH 027/100] Always use multiple script selection --- src/Program.cs | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index bcec708..4013708 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -12,7 +12,6 @@ static async Task RootCommand( [Option("extensions", new[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, [Option("depth", new[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, [Option("elevated", new[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("multiple", new[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, [Option("grouped", new[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { @@ -37,8 +36,8 @@ static async Task RootCommand( return; } - var prompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(prompt); + var dirPrompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); files = dict[directoryInfo]; } else @@ -53,21 +52,12 @@ static async Task RootCommand( return; } - if (multiple) - { - var prompt = PromptConstructor.GetMultiSelectionPrompt(files); - var scripts = AnsiConsole.Prompt(prompt); + + var prompt = PromptConstructor.GetMultiSelectionPrompt(files); + var scripts = AnsiConsole.Prompt(prompt); - await ScriptExecutor.ExecAsync(scripts, elevated); - } - else - { - var prompt = PromptConstructor.GetSelectionPrompt(files); - - var script = AnsiConsole.Prompt(prompt); - - await ScriptExecutor.ExecAsync(script, elevated); - } + await ScriptExecutor.ExecAsync(scripts, elevated); + } static class PromptConstructor @@ -83,19 +73,6 @@ static class PromptConstructor private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; - public static SelectionPrompt GetSelectionPrompt(FileInfo[] files) - { - var prompt = new SelectionPrompt() - .Title("Select a script to execute:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(FileStyle) - .HighlightStyle(SelectionHighlight) - .AddChoices(files); - - return prompt; - } - public static MultiSelectionPrompt GetMultiSelectionPrompt(FileInfo[] files) { var prompt = new MultiSelectionPrompt() From 0293fe85cb5b1bee1ce7f64853deae04aa28025d Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 24 May 2022 18:46:04 +0200 Subject: [PATCH 028/100] Always use multiple script selection --- src/Program.cs | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index bcec708..4013708 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -12,7 +12,6 @@ static async Task RootCommand( [Option("extensions", new[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, [Option("depth", new[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, [Option("elevated", new[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("multiple", new[] { 'm' }, Description = "Execute multiple scripts in parallel")] bool multiple = false, [Option("grouped", new[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") { @@ -37,8 +36,8 @@ static async Task RootCommand( return; } - var prompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(prompt); + var dirPrompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); files = dict[directoryInfo]; } else @@ -53,21 +52,12 @@ static async Task RootCommand( return; } - if (multiple) - { - var prompt = PromptConstructor.GetMultiSelectionPrompt(files); - var scripts = AnsiConsole.Prompt(prompt); + + var prompt = PromptConstructor.GetMultiSelectionPrompt(files); + var scripts = AnsiConsole.Prompt(prompt); - await ScriptExecutor.ExecAsync(scripts, elevated); - } - else - { - var prompt = PromptConstructor.GetSelectionPrompt(files); - - var script = AnsiConsole.Prompt(prompt); - - await ScriptExecutor.ExecAsync(script, elevated); - } + await ScriptExecutor.ExecAsync(scripts, elevated); + } static class PromptConstructor @@ -83,19 +73,6 @@ static class PromptConstructor private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; - public static SelectionPrompt GetSelectionPrompt(FileInfo[] files) - { - var prompt = new SelectionPrompt() - .Title("Select a script to execute:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(FileStyle) - .HighlightStyle(SelectionHighlight) - .AddChoices(files); - - return prompt; - } - public static MultiSelectionPrompt GetMultiSelectionPrompt(FileInfo[] files) { var prompt = new MultiSelectionPrompt() From 150685acee42746a5fa9e095f4c244169d4ac19e Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 24 May 2022 20:26:13 +0200 Subject: [PATCH 029/100] Rename CLI args --- src/Program.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 4013708..a8c2eea 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -9,11 +9,11 @@ app.AddCommand(RootCommand); app.Run(); static async Task RootCommand( - [Option("extensions", new[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, - [Option("depth", new[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, - [Option("elevated", new[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("grouped", new[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, - [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") + [Option("extensions", new[] { 'x' }, Description = "Comma separated list of script extensions")] string? extensions, + [Option("depth", new[] { 'd' }, Description = "Search depth")] int depth = 1, + [Option("elevated", new[] { 'e' }, Description = "Run with elevated privileges")] bool elevated = false, + [Option("group", new[] { 'g' }, Description = "Group scripts by folder")] bool group = false, + [Argument(Name = "Directory", Description = "Starting directory")] string directory = ".") { if (!Directory.Exists(directory)) { @@ -25,7 +25,7 @@ static async Task RootCommand( FileInfo[] files; var finder = new ScriptFinder(extensions, directory, depth); - if (grouped) + if (group) { var dict = finder.GetScriptsByDirectory(); From 26e5404c1a75ecfd68367078c39fdd5ad08de0f7 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 24 May 2022 20:26:13 +0200 Subject: [PATCH 030/100] Rename CLI args --- src/Program.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 4013708..a8c2eea 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -9,11 +9,11 @@ app.AddCommand(RootCommand); app.Run(); static async Task RootCommand( - [Option("extensions", new[] { 'e' }, Description = "Comma separated list of script extensions to search")] string? extensions, - [Option("depth", new[] { 'd' }, Description = "Folder depth of the search")] int depth = 1, - [Option("elevated", new[] { 'E' }, Description = "Run the script with elevated privileges")] bool elevated = false, - [Option("grouped", new[] { 'g' }, Description = "Group selection bay containing folder")] bool grouped = false, - [Argument(Name = "Directory", Description = "Directory from which search the scripts")] string directory = ".") + [Option("extensions", new[] { 'x' }, Description = "Comma separated list of script extensions")] string? extensions, + [Option("depth", new[] { 'd' }, Description = "Search depth")] int depth = 1, + [Option("elevated", new[] { 'e' }, Description = "Run with elevated privileges")] bool elevated = false, + [Option("group", new[] { 'g' }, Description = "Group scripts by folder")] bool group = false, + [Argument(Name = "Directory", Description = "Starting directory")] string directory = ".") { if (!Directory.Exists(directory)) { @@ -25,7 +25,7 @@ static async Task RootCommand( FileInfo[] files; var finder = new ScriptFinder(extensions, directory, depth); - if (grouped) + if (group) { var dict = finder.GetScriptsByDirectory(); From a4fe1ba84aa0b4a04ffdf826f1bbb5a00f0debb1 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 25 May 2022 18:20:53 +0200 Subject: [PATCH 031/100] Allow installation as dotnet tool --- dotnet-install-tool.ps1 | 2 ++ src/ScriptLauncher.csproj | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 dotnet-install-tool.ps1 diff --git a/dotnet-install-tool.ps1 b/dotnet-install-tool.ps1 new file mode 100644 index 0000000..4815a6d --- /dev/null +++ b/dotnet-install-tool.ps1 @@ -0,0 +1,2 @@ +dotnet pack ./src -o ./nupkg +dotnet tool install -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources \ No newline at end of file diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index febac53..ac43673 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -6,6 +6,8 @@ enable enable 0.1.0 + true + script-launcher From 2e9d49768d4407fa276b8bee2f141cb24a04c269 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 25 May 2022 18:20:53 +0200 Subject: [PATCH 032/100] Allow installation as dotnet tool --- dotnet-install-tool.ps1 | 2 ++ src/ScriptLauncher.csproj | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 dotnet-install-tool.ps1 diff --git a/dotnet-install-tool.ps1 b/dotnet-install-tool.ps1 new file mode 100644 index 0000000..4815a6d --- /dev/null +++ b/dotnet-install-tool.ps1 @@ -0,0 +1,2 @@ +dotnet pack ./src -o ./nupkg +dotnet tool install -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources \ No newline at end of file diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index febac53..ac43673 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -6,6 +6,8 @@ enable enable 0.1.0 + true + script-launcher From ae15e9abe4edf55d72f8935efb7319bbd37a5ed5 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 16:25:12 +0200 Subject: [PATCH 033/100] Allow dispaly of the filename (& extension) only --- src/Program.cs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index a8c2eea..40cad55 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Diagnostics; +using System.Text; using Cocona; using Spectre.Console; @@ -13,6 +14,7 @@ static async Task RootCommand( [Option("depth", new[] { 'd' }, Description = "Search depth")] int depth = 1, [Option("elevated", new[] { 'e' }, Description = "Run with elevated privileges")] bool elevated = false, [Option("group", new[] { 'g' }, Description = "Group scripts by folder")] bool group = false, + [Option("brief", new[] { 'b' }, Description = "Show brief information")] bool brief = false, [Argument(Name = "Directory", Description = "Starting directory")] string directory = ".") { if (!Directory.Exists(directory)) @@ -36,7 +38,7 @@ static async Task RootCommand( return; } - var dirPrompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); var directoryInfo = AnsiConsole.Prompt(dirPrompt); files = dict[directoryInfo]; } @@ -52,12 +54,12 @@ static async Task RootCommand( return; } - - var prompt = PromptConstructor.GetMultiSelectionPrompt(files); + + var prompt = PromptConstructor.GetScriptPrompt(files, brief); var scripts = AnsiConsole.Prompt(prompt); await ScriptExecutor.ExecAsync(scripts, elevated); - + } static class PromptConstructor @@ -66,14 +68,25 @@ static class PromptConstructor private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); - private static string FileStyle(FileInfo info) => - $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]" - + $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]" - + $"[greenyellow]{info.Extension}[/]"; + private static string FileStyle(FileInfo info, bool brief) + { + var builder = new StringBuilder(); + + if (!brief) + { + builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); + } + + builder + .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") + .Append($"[greenyellow]{info.Extension}[/]"); + + return builder.ToString(); + } private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; - public static MultiSelectionPrompt GetMultiSelectionPrompt(FileInfo[] files) + public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) { var prompt = new MultiSelectionPrompt() .Title("Select a script the scripts to execute:") @@ -81,14 +94,14 @@ static class PromptConstructor .PageSize(ScriptListSize) .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(FileStyle) + .UseConverter(x => FileStyle(x, brief)) .HighlightStyle(SelectionHighlight) .AddChoices(files); return prompt; } - public static SelectionPrompt GetSelectionPrompt(DirectoryInfo[] directories) + public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) { var prompt = new SelectionPrompt() .Title("Select a directory:") From ab99b102a0cc079e40245473dfd4cccaf8c0a234 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 16:25:12 +0200 Subject: [PATCH 034/100] Allow dispaly of the filename (& extension) only --- src/Program.cs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index a8c2eea..40cad55 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Diagnostics; +using System.Text; using Cocona; using Spectre.Console; @@ -13,6 +14,7 @@ static async Task RootCommand( [Option("depth", new[] { 'd' }, Description = "Search depth")] int depth = 1, [Option("elevated", new[] { 'e' }, Description = "Run with elevated privileges")] bool elevated = false, [Option("group", new[] { 'g' }, Description = "Group scripts by folder")] bool group = false, + [Option("brief", new[] { 'b' }, Description = "Show brief information")] bool brief = false, [Argument(Name = "Directory", Description = "Starting directory")] string directory = ".") { if (!Directory.Exists(directory)) @@ -36,7 +38,7 @@ static async Task RootCommand( return; } - var dirPrompt = PromptConstructor.GetSelectionPrompt(dict.Keys.ToArray()); + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); var directoryInfo = AnsiConsole.Prompt(dirPrompt); files = dict[directoryInfo]; } @@ -52,12 +54,12 @@ static async Task RootCommand( return; } - - var prompt = PromptConstructor.GetMultiSelectionPrompt(files); + + var prompt = PromptConstructor.GetScriptPrompt(files, brief); var scripts = AnsiConsole.Prompt(prompt); await ScriptExecutor.ExecAsync(scripts, elevated); - + } static class PromptConstructor @@ -66,14 +68,25 @@ static class PromptConstructor private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); - private static string FileStyle(FileInfo info) => - $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]" - + $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]" - + $"[greenyellow]{info.Extension}[/]"; + private static string FileStyle(FileInfo info, bool brief) + { + var builder = new StringBuilder(); + + if (!brief) + { + builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); + } + + builder + .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") + .Append($"[greenyellow]{info.Extension}[/]"); + + return builder.ToString(); + } private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; - public static MultiSelectionPrompt GetMultiSelectionPrompt(FileInfo[] files) + public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) { var prompt = new MultiSelectionPrompt() .Title("Select a script the scripts to execute:") @@ -81,14 +94,14 @@ static class PromptConstructor .PageSize(ScriptListSize) .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(FileStyle) + .UseConverter(x => FileStyle(x, brief)) .HighlightStyle(SelectionHighlight) .AddChoices(files); return prompt; } - public static SelectionPrompt GetSelectionPrompt(DirectoryInfo[] directories) + public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) { var prompt = new SelectionPrompt() .Title("Select a directory:") From 9c83f6e04645e025116af0de4aad36de97db9ae8 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 16:30:57 +0200 Subject: [PATCH 035/100] Split types in dedicated files --- src/Program.cs | 178 +-------------------------------------- src/PromptConstructor.cs | 55 ++++++++++++ src/ScriptExecutor.cs | 70 +++++++++++++++ src/ScriptFinder.cs | 52 ++++++++++++ 4 files changed, 178 insertions(+), 177 deletions(-) create mode 100644 src/PromptConstructor.cs create mode 100644 src/ScriptExecutor.cs create mode 100644 src/ScriptFinder.cs diff --git a/src/Program.cs b/src/Program.cs index 40cad55..5f197e0 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,7 +1,4 @@ -using System.ComponentModel; -using System.Diagnostics; -using System.Text; -using Cocona; +using Cocona; using Spectre.Console; var app = CoconaLiteApp.Create(); @@ -54,181 +51,8 @@ static async Task RootCommand( return; } - var prompt = PromptConstructor.GetScriptPrompt(files, brief); var scripts = AnsiConsole.Prompt(prompt); await ScriptExecutor.ExecAsync(scripts, elevated); - -} - -static class PromptConstructor -{ - const int ScriptListSize = 15; - - private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); - - private static string FileStyle(FileInfo info, bool brief) - { - var builder = new StringBuilder(); - - if (!brief) - { - builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); - } - - builder - .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") - .Append($"[greenyellow]{info.Extension}[/]"); - - return builder.ToString(); - } - - private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; - - public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) - { - var prompt = new MultiSelectionPrompt() - .Title("Select a script the scripts to execute:") - .NotRequired() - .PageSize(ScriptListSize) - .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => FileStyle(x, brief)) - .HighlightStyle(SelectionHighlight) - .AddChoices(files); - - return prompt; - } - - public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) - { - var prompt = new SelectionPrompt() - .Title("Select a directory:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(DirectoryStyle) - .HighlightStyle(SelectionHighlight) - .AddChoices(directories); - - return prompt; - } -} - -static class ScriptExecutor -{ - public static async Task ExecAsync(List files, bool elevated) - { - await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); - } - - public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) - { - var process = GetExecutableProcessInfo(file, elevated); - - if (process is null) return; - - try - { - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); - } - catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) - { - AnsiConsole.Markup($"[red]{ex.Message}[/]"); - } - } - - private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) - { - return file.Extension switch - { - ".bat" or ".cmd" => new ProcessStartInfo - { - FileName = "cmd", - Arguments = $"/Q /C .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".ps1" => new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".sh" => new ProcessStartInfo - { - FileName = "bash", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".zsh" => new ProcessStartInfo - { - FileName = "zsh", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".fish" => new ProcessStartInfo - { - FileName = "fish", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - _ => null - }; - } -} - -readonly struct ScriptFinder -{ - static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; - public string[] Extensions { get; } - public string RootDirectory { get; } - public int Depth { get; } - private readonly EnumerationOptions _options; - - public ScriptFinder(string? extensions, string directory, int depth) - { - Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .ToHashSet() - .Select(x => $".{x.TrimStart('.')}") - .ToArray() ?? DefaultExtensions; - - Depth = depth; - RootDirectory = directory; - - _options = new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = Depth > 0, - MaxRecursionDepth = Depth, - }; - } - - private IEnumerable GetScriptFilesWithExtension(string extension) - { - try - { - var filenames = Directory.GetFiles(RootDirectory, $"*{extension}", _options); - return filenames.Select(x => new FileInfo(x)); - } - catch (UnauthorizedAccessException) - { - return Enumerable.Empty(); - } - } - - public FileInfo[] GetScripts() => - Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); - - public IDictionary GetScriptsByDirectory() => - Extensions - .Select(GetScriptFilesWithExtension) - .SelectMany(x => x) - .GroupBy(x => x.DirectoryName!) - .OrderBy(x => x.Key) - .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); } diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs new file mode 100644 index 0000000..ba42adc --- /dev/null +++ b/src/PromptConstructor.cs @@ -0,0 +1,55 @@ +using System.Text; +using Spectre.Console; + +static class PromptConstructor +{ + const int ScriptListSize = 15; + + private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + + private static string FileStyle(FileInfo info, bool brief) + { + var builder = new StringBuilder(); + + if (!brief) + { + builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); + } + + builder + .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") + .Append($"[greenyellow]{info.Extension}[/]"); + + return builder.ToString(); + } + + private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + + public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) + { + var prompt = new MultiSelectionPrompt() + .Title("Select a script the scripts to execute:") + .NotRequired() + .PageSize(ScriptListSize) + .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(x => FileStyle(x, brief)) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); + + return prompt; + } + + public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) + { + var prompt = new SelectionPrompt() + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(DirectoryStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(directories); + + return prompt; + } +} diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs new file mode 100644 index 0000000..a80e9b3 --- /dev/null +++ b/src/ScriptExecutor.cs @@ -0,0 +1,70 @@ +using System.ComponentModel; +using System.Diagnostics; +using Spectre.Console; + +static class ScriptExecutor +{ + public static async Task ExecAsync(List files, bool elevated) + { + await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); + } + + public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + { + var process = GetExecutableProcessInfo(file, elevated); + + if (process is null) return; + + try + { + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + } + catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + } + } + + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) + { + return file.Extension switch + { + ".bat" or ".cmd" => new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" => new ProcessStartInfo + { + FileName = "bash", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".zsh" => new ProcessStartInfo + { + FileName = "zsh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".fish" => new ProcessStartInfo + { + FileName = "fish", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + _ => null + }; + } +} diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs new file mode 100644 index 0000000..58c512a --- /dev/null +++ b/src/ScriptFinder.cs @@ -0,0 +1,52 @@ +using Spectre.Console; + +readonly struct ScriptFinder +{ + static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + public string[] Extensions { get; } + public string RootDirectory { get; } + public int Depth { get; } + private readonly EnumerationOptions _options; + + public ScriptFinder(string? extensions, string directory, int depth) + { + Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .Select(x => $".{x.TrimStart('.')}") + .ToArray() ?? DefaultExtensions; + + Depth = depth; + RootDirectory = directory; + + _options = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; + } + + private IEnumerable GetScriptFilesWithExtension(string extension) + { + try + { + var filenames = Directory.GetFiles(RootDirectory, $"*{extension}", _options); + return filenames.Select(x => new FileInfo(x)); + } + catch (UnauthorizedAccessException) + { + return Enumerable.Empty(); + } + } + + public FileInfo[] GetScripts() => + Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); + + public IDictionary GetScriptsByDirectory() => + Extensions + .Select(GetScriptFilesWithExtension) + .SelectMany(x => x) + .GroupBy(x => x.DirectoryName!) + .OrderBy(x => x.Key) + .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); +} From 5c24b9ff09a35bb278b1759271809b570da7f814 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 16:30:57 +0200 Subject: [PATCH 036/100] Split types in dedicated files --- src/Program.cs | 178 +-------------------------------------- src/PromptConstructor.cs | 55 ++++++++++++ src/ScriptExecutor.cs | 70 +++++++++++++++ src/ScriptFinder.cs | 52 ++++++++++++ 4 files changed, 178 insertions(+), 177 deletions(-) create mode 100644 src/PromptConstructor.cs create mode 100644 src/ScriptExecutor.cs create mode 100644 src/ScriptFinder.cs diff --git a/src/Program.cs b/src/Program.cs index 40cad55..5f197e0 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,7 +1,4 @@ -using System.ComponentModel; -using System.Diagnostics; -using System.Text; -using Cocona; +using Cocona; using Spectre.Console; var app = CoconaLiteApp.Create(); @@ -54,181 +51,8 @@ static async Task RootCommand( return; } - var prompt = PromptConstructor.GetScriptPrompt(files, brief); var scripts = AnsiConsole.Prompt(prompt); await ScriptExecutor.ExecAsync(scripts, elevated); - -} - -static class PromptConstructor -{ - const int ScriptListSize = 15; - - private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); - - private static string FileStyle(FileInfo info, bool brief) - { - var builder = new StringBuilder(); - - if (!brief) - { - builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); - } - - builder - .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") - .Append($"[greenyellow]{info.Extension}[/]"); - - return builder.ToString(); - } - - private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; - - public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) - { - var prompt = new MultiSelectionPrompt() - .Title("Select a script the scripts to execute:") - .NotRequired() - .PageSize(ScriptListSize) - .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => FileStyle(x, brief)) - .HighlightStyle(SelectionHighlight) - .AddChoices(files); - - return prompt; - } - - public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) - { - var prompt = new SelectionPrompt() - .Title("Select a directory:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(DirectoryStyle) - .HighlightStyle(SelectionHighlight) - .AddChoices(directories); - - return prompt; - } -} - -static class ScriptExecutor -{ - public static async Task ExecAsync(List files, bool elevated) - { - await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); - } - - public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) - { - var process = GetExecutableProcessInfo(file, elevated); - - if (process is null) return; - - try - { - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); - } - catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) - { - AnsiConsole.Markup($"[red]{ex.Message}[/]"); - } - } - - private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) - { - return file.Extension switch - { - ".bat" or ".cmd" => new ProcessStartInfo - { - FileName = "cmd", - Arguments = $"/Q /C .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".ps1" => new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".sh" => new ProcessStartInfo - { - FileName = "bash", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".zsh" => new ProcessStartInfo - { - FileName = "zsh", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".fish" => new ProcessStartInfo - { - FileName = "fish", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - _ => null - }; - } -} - -readonly struct ScriptFinder -{ - static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; - public string[] Extensions { get; } - public string RootDirectory { get; } - public int Depth { get; } - private readonly EnumerationOptions _options; - - public ScriptFinder(string? extensions, string directory, int depth) - { - Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .ToHashSet() - .Select(x => $".{x.TrimStart('.')}") - .ToArray() ?? DefaultExtensions; - - Depth = depth; - RootDirectory = directory; - - _options = new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = Depth > 0, - MaxRecursionDepth = Depth, - }; - } - - private IEnumerable GetScriptFilesWithExtension(string extension) - { - try - { - var filenames = Directory.GetFiles(RootDirectory, $"*{extension}", _options); - return filenames.Select(x => new FileInfo(x)); - } - catch (UnauthorizedAccessException) - { - return Enumerable.Empty(); - } - } - - public FileInfo[] GetScripts() => - Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); - - public IDictionary GetScriptsByDirectory() => - Extensions - .Select(GetScriptFilesWithExtension) - .SelectMany(x => x) - .GroupBy(x => x.DirectoryName!) - .OrderBy(x => x.Key) - .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); } diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs new file mode 100644 index 0000000..ba42adc --- /dev/null +++ b/src/PromptConstructor.cs @@ -0,0 +1,55 @@ +using System.Text; +using Spectre.Console; + +static class PromptConstructor +{ + const int ScriptListSize = 15; + + private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + + private static string FileStyle(FileInfo info, bool brief) + { + var builder = new StringBuilder(); + + if (!brief) + { + builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); + } + + builder + .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") + .Append($"[greenyellow]{info.Extension}[/]"); + + return builder.ToString(); + } + + private static string DirectoryStyle(DirectoryInfo info) => $"[blue]{info}[/]"; + + public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) + { + var prompt = new MultiSelectionPrompt() + .Title("Select a script the scripts to execute:") + .NotRequired() + .PageSize(ScriptListSize) + .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(x => FileStyle(x, brief)) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); + + return prompt; + } + + public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) + { + var prompt = new SelectionPrompt() + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(DirectoryStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(directories); + + return prompt; + } +} diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs new file mode 100644 index 0000000..a80e9b3 --- /dev/null +++ b/src/ScriptExecutor.cs @@ -0,0 +1,70 @@ +using System.ComponentModel; +using System.Diagnostics; +using Spectre.Console; + +static class ScriptExecutor +{ + public static async Task ExecAsync(List files, bool elevated) + { + await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); + } + + public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + { + var process = GetExecutableProcessInfo(file, elevated); + + if (process is null) return; + + try + { + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + } + catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + } + } + + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) + { + return file.Extension switch + { + ".bat" or ".cmd" => new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" => new ProcessStartInfo + { + FileName = "bash", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".zsh" => new ProcessStartInfo + { + FileName = "zsh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".fish" => new ProcessStartInfo + { + FileName = "fish", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + _ => null + }; + } +} diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs new file mode 100644 index 0000000..58c512a --- /dev/null +++ b/src/ScriptFinder.cs @@ -0,0 +1,52 @@ +using Spectre.Console; + +readonly struct ScriptFinder +{ + static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + public string[] Extensions { get; } + public string RootDirectory { get; } + public int Depth { get; } + private readonly EnumerationOptions _options; + + public ScriptFinder(string? extensions, string directory, int depth) + { + Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .Select(x => $".{x.TrimStart('.')}") + .ToArray() ?? DefaultExtensions; + + Depth = depth; + RootDirectory = directory; + + _options = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = Depth > 0, + MaxRecursionDepth = Depth, + }; + } + + private IEnumerable GetScriptFilesWithExtension(string extension) + { + try + { + var filenames = Directory.GetFiles(RootDirectory, $"*{extension}", _options); + return filenames.Select(x => new FileInfo(x)); + } + catch (UnauthorizedAccessException) + { + return Enumerable.Empty(); + } + } + + public FileInfo[] GetScripts() => + Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); + + public IDictionary GetScriptsByDirectory() => + Extensions + .Select(GetScriptFilesWithExtension) + .SelectMany(x => x) + .GroupBy(x => x.DirectoryName!) + .OrderBy(x => x.Key) + .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); +} From dc40ced0336a00d4b7872dd3aa22f9ac0e0a77fa Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 17:10:41 +0200 Subject: [PATCH 037/100] Update install scripts to install or update --- dotnet-install-tool.ps1 | 7 ++++++- dotnet-install-tool.sh | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 dotnet-install-tool.sh diff --git a/dotnet-install-tool.ps1 b/dotnet-install-tool.ps1 index 4815a6d..b26f6ee 100644 --- a/dotnet-install-tool.ps1 +++ b/dotnet-install-tool.ps1 @@ -1,2 +1,7 @@ dotnet pack ./src -o ./nupkg -dotnet tool install -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources \ No newline at end of file + +$exists = $(Test-CommandExists script-launcher) +$action = $exists ? 'update' : 'install' + +dotnet tool $action -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources + diff --git a/dotnet-install-tool.sh b/dotnet-install-tool.sh new file mode 100644 index 0000000..bf94705 --- /dev/null +++ b/dotnet-install-tool.sh @@ -0,0 +1,13 @@ +#! /usr/bin/env bash + +dotnet pack ./src -o ./nupkg + +EXISTS=$(command -v script-launcher) + +if $EXISTS; then + ACTION="update" +else + ACTION="install" +fi + +dotnet tool "$ACTION" -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources From 3daa10f3f871c9940eb9854245ec21e98dbc161f Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 17:10:41 +0200 Subject: [PATCH 038/100] Update install scripts to install or update --- dotnet-install-tool.ps1 | 7 ++++++- dotnet-install-tool.sh | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 dotnet-install-tool.sh diff --git a/dotnet-install-tool.ps1 b/dotnet-install-tool.ps1 index 4815a6d..b26f6ee 100644 --- a/dotnet-install-tool.ps1 +++ b/dotnet-install-tool.ps1 @@ -1,2 +1,7 @@ dotnet pack ./src -o ./nupkg -dotnet tool install -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources \ No newline at end of file + +$exists = $(Test-CommandExists script-launcher) +$action = $exists ? 'update' : 'install' + +dotnet tool $action -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources + diff --git a/dotnet-install-tool.sh b/dotnet-install-tool.sh new file mode 100644 index 0000000..bf94705 --- /dev/null +++ b/dotnet-install-tool.sh @@ -0,0 +1,13 @@ +#! /usr/bin/env bash + +dotnet pack ./src -o ./nupkg + +EXISTS=$(command -v script-launcher) + +if $EXISTS; then + ACTION="update" +else + ACTION="install" +fi + +dotnet tool "$ACTION" -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources From 6a015779125ca673762d60a588736ac2078da8cd Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 17:45:24 +0200 Subject: [PATCH 039/100] Update README & install scripts --- README.md | 52 ++++++++++++++++++- dotnet-publish.ps1 | 26 ---------- ...all-tool.ps1 => install-as-dotnet-tool.ps1 | 0 ...stall-tool.sh => install-as-dotnet-tool.sh | 4 +- 4 files changed, 53 insertions(+), 29 deletions(-) delete mode 100644 dotnet-publish.ps1 rename dotnet-install-tool.ps1 => install-as-dotnet-tool.ps1 (100%) rename dotnet-install-tool.sh => install-as-dotnet-tool.sh (82%) mode change 100644 => 100755 diff --git a/README.md b/README.md index 438a0fd..428a902 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,53 @@ # Script Launcher -Find executable scripts in a directory and allow to select which one to execute +Find executable scripts in a directory and allow to select which one to execute. + +## Installation + +### As .NET CLI Tool + +To be installed as a tool, you need the [.NET CLI][CLI] which is included in the [.NET SDK][SDK] + +Install manually using the following commands or by using the provided installation scripts. + +```sh +dotnet pack ./src -o ./nupkg +dotnet tool install -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources +``` + +### As a Standalone Executable + +You will need the identifier of the .NET Runtime ([RID]) and the Target Framework Moniker ([TFM]) for your OS and Runtime. + +```sh +# self contained: will not need the .NET runtime to function, bigger resulting size +dotnet publish -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f -r -o ./src + +# no self contained: will need the .NET runtime to be installed to function, smallest size +dotnet publish -c Release --no-self-contained -p:PublishSingleFile=true -r -o ./src +``` + +_**NOTE**_: The option `-p:PublishTrimmed=true` may produce some *warnings*. If so simply skip that option and the resulting executable will be larger + +## Usage + +```sh +Usage: script-launcher [--extensions ] [--depth ] [--elevated] [--group] [--brief] [--help] [--version] directory + +Arguments: + 0: directory Starting directory (Default: .) + +Options: + -x, --extensions Comma separated list of script extensions + -d, --depth Search depth (Default: 1) + -e, --elevated Run with elevated privileges + -g, --group Group scripts by folder + -b, --brief Show brief information + -h, --help Show help message + --version Show version +``` + +[CLI]: https://docs.microsoft.com/en-us/dotnet/core/tools/ ".NET CLI Docs" +[SDK]: https://dotnet.microsoft.com/en-us/download ".NET SDK Downloads" +[RID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog "Runtime IDs Catalog" +[TFM]: https://docs.microsoft.com/en-us/dotnet/standard/frameworks "Target Framework Moniker Docs" diff --git a/dotnet-publish.ps1 b/dotnet-publish.ps1 deleted file mode 100644 index 43860c0..0000000 --- a/dotnet-publish.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -function Get-Runtime -{ - if($isLinux) - { - return "linux-x64"; - } - - if($isMac) - { - return "osx-x64"; - } - - if($isWindows) - { - return "win-x64"; - } - - return $null; -} - -$runtime = Get-Runtime; - -if($runtime -ne $null) -{ - dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r $runtime .\src\ -} \ No newline at end of file diff --git a/dotnet-install-tool.ps1 b/install-as-dotnet-tool.ps1 similarity index 100% rename from dotnet-install-tool.ps1 rename to install-as-dotnet-tool.ps1 diff --git a/dotnet-install-tool.sh b/install-as-dotnet-tool.sh old mode 100644 new mode 100755 similarity index 82% rename from dotnet-install-tool.sh rename to install-as-dotnet-tool.sh index bf94705..4b0924f --- a/dotnet-install-tool.sh +++ b/install-as-dotnet-tool.sh @@ -1,10 +1,10 @@ -#! /usr/bin/env bash +#!/usr/bin/env bash dotnet pack ./src -o ./nupkg EXISTS=$(command -v script-launcher) -if $EXISTS; then +if [ "$EXISTS" ]; then ACTION="update" else ACTION="install" From fd4bbce4b172a70c7d4937cb1e3ae25edd072156 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 10 Jun 2022 17:45:24 +0200 Subject: [PATCH 040/100] Update README & install scripts --- README.md | 52 ++++++++++++++++++- dotnet-publish.ps1 | 26 ---------- ...all-tool.ps1 => install-as-dotnet-tool.ps1 | 0 ...stall-tool.sh => install-as-dotnet-tool.sh | 4 +- 4 files changed, 53 insertions(+), 29 deletions(-) delete mode 100644 dotnet-publish.ps1 rename dotnet-install-tool.ps1 => install-as-dotnet-tool.ps1 (100%) rename dotnet-install-tool.sh => install-as-dotnet-tool.sh (82%) mode change 100644 => 100755 diff --git a/README.md b/README.md index 438a0fd..428a902 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,53 @@ # Script Launcher -Find executable scripts in a directory and allow to select which one to execute +Find executable scripts in a directory and allow to select which one to execute. + +## Installation + +### As .NET CLI Tool + +To be installed as a tool, you need the [.NET CLI][CLI] which is included in the [.NET SDK][SDK] + +Install manually using the following commands or by using the provided installation scripts. + +```sh +dotnet pack ./src -o ./nupkg +dotnet tool install -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources +``` + +### As a Standalone Executable + +You will need the identifier of the .NET Runtime ([RID]) and the Target Framework Moniker ([TFM]) for your OS and Runtime. + +```sh +# self contained: will not need the .NET runtime to function, bigger resulting size +dotnet publish -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f -r -o ./src + +# no self contained: will need the .NET runtime to be installed to function, smallest size +dotnet publish -c Release --no-self-contained -p:PublishSingleFile=true -r -o ./src +``` + +_**NOTE**_: The option `-p:PublishTrimmed=true` may produce some *warnings*. If so simply skip that option and the resulting executable will be larger + +## Usage + +```sh +Usage: script-launcher [--extensions ] [--depth ] [--elevated] [--group] [--brief] [--help] [--version] directory + +Arguments: + 0: directory Starting directory (Default: .) + +Options: + -x, --extensions Comma separated list of script extensions + -d, --depth Search depth (Default: 1) + -e, --elevated Run with elevated privileges + -g, --group Group scripts by folder + -b, --brief Show brief information + -h, --help Show help message + --version Show version +``` + +[CLI]: https://docs.microsoft.com/en-us/dotnet/core/tools/ ".NET CLI Docs" +[SDK]: https://dotnet.microsoft.com/en-us/download ".NET SDK Downloads" +[RID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog "Runtime IDs Catalog" +[TFM]: https://docs.microsoft.com/en-us/dotnet/standard/frameworks "Target Framework Moniker Docs" diff --git a/dotnet-publish.ps1 b/dotnet-publish.ps1 deleted file mode 100644 index 43860c0..0000000 --- a/dotnet-publish.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -function Get-Runtime -{ - if($isLinux) - { - return "linux-x64"; - } - - if($isMac) - { - return "osx-x64"; - } - - if($isWindows) - { - return "win-x64"; - } - - return $null; -} - -$runtime = Get-Runtime; - -if($runtime -ne $null) -{ - dotnet publish -o out -c Release --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -f net6.0 -r $runtime .\src\ -} \ No newline at end of file diff --git a/dotnet-install-tool.ps1 b/install-as-dotnet-tool.ps1 similarity index 100% rename from dotnet-install-tool.ps1 rename to install-as-dotnet-tool.ps1 diff --git a/dotnet-install-tool.sh b/install-as-dotnet-tool.sh old mode 100644 new mode 100755 similarity index 82% rename from dotnet-install-tool.sh rename to install-as-dotnet-tool.sh index bf94705..4b0924f --- a/dotnet-install-tool.sh +++ b/install-as-dotnet-tool.sh @@ -1,10 +1,10 @@ -#! /usr/bin/env bash +#!/usr/bin/env bash dotnet pack ./src -o ./nupkg EXISTS=$(command -v script-launcher) -if $EXISTS; then +if [ "$EXISTS" ]; then ACTION="update" else ACTION="install" From e9f2ad9bdd2881d97876aeaf8b48041532cbd802 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 11 Jun 2022 17:24:02 +0200 Subject: [PATCH 041/100] Create dependabot.yml --- .github/dependabot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..56a27eb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/src" # Location of package manifests + schedule: + interval: "weekly" + commit-message: + prefix: "Composer" + include: "scope" From 4f1aba962aee56e70ed19f76a5f32e11367ece86 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 11 Jun 2022 17:24:02 +0200 Subject: [PATCH 042/100] Create dependabot.yml --- .github/dependabot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..56a27eb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/src" # Location of package manifests + schedule: + interval: "weekly" + commit-message: + prefix: "Composer" + include: "scope" From 62fb1402a7df955db18038ca86141581d1e92108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Jun 2022 15:24:24 +0000 Subject: [PATCH 043/100] Composer(deps): Bump Spectre.Console from 0.43.0 to 0.44.0 in /src Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.43.0 to 0.44.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.43.0...0.44.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index ac43673..313ec18 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From 01cf195b9c37e51b0958f079405f8d9b7b79a705 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Jun 2022 15:24:24 +0000 Subject: [PATCH 044/100] Composer(deps): Bump Spectre.Console from 0.43.0 to 0.44.0 in /src Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.43.0 to 0.44.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.43.0...0.44.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index ac43673..313ec18 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From 0c70ee0f1ba250620cf230e7085be4a3f82c64fb Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sun, 12 Jun 2022 11:36:54 +0200 Subject: [PATCH 045/100] Update dependabot.yml --- .github/dependabot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 56a27eb..105324d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,10 +5,10 @@ version: 2 updates: - - package-ecosystem: "nuget" # See documentation for possible values - directory: "/src" # Location of package manifests + - package-ecosystem: "nuget" + directory: "/src" schedule: interval: "weekly" commit-message: - prefix: "Composer" + prefix: "NuGet" include: "scope" From 36c79e02bc89610980fbb32163e925320327db39 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sun, 12 Jun 2022 11:36:54 +0200 Subject: [PATCH 046/100] Update dependabot.yml --- .github/dependabot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 56a27eb..105324d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,10 +5,10 @@ version: 2 updates: - - package-ecosystem: "nuget" # See documentation for possible values - directory: "/src" # Location of package manifests + - package-ecosystem: "nuget" + directory: "/src" schedule: interval: "weekly" commit-message: - prefix: "Composer" + prefix: "NuGet" include: "scope" From 7a4dc8dc8ccd22ec8f6c1b4c798503345257ec5d Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 13 Jun 2022 09:23:50 +0200 Subject: [PATCH 047/100] Fix: powershell install script - add missing `Test-CommandExists` function definition - add shebang --- install-as-dotnet-tool.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/install-as-dotnet-tool.ps1 b/install-as-dotnet-tool.ps1 index b26f6ee..32f1313 100644 --- a/install-as-dotnet-tool.ps1 +++ b/install-as-dotnet-tool.ps1 @@ -1,4 +1,16 @@ -dotnet pack ./src -o ./nupkg +#!/usr/bin/env pwsh + +function Test-CommandExists([Parameter(Mandatory)] [string] $command) +{ + try { + if (Get-Command $command -ErrorAction Stop) { return $true } + } catch { + return $false + } +} + + +dotnet pack ./src -o ./nupkg $exists = $(Test-CommandExists script-launcher) $action = $exists ? 'update' : 'install' From 4ef34ac2bc9d456adfbf5c805bebc9b62922d812 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Mon, 13 Jun 2022 09:23:50 +0200 Subject: [PATCH 048/100] Fix: powershell install script - add missing `Test-CommandExists` function definition - add shebang --- install-as-dotnet-tool.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/install-as-dotnet-tool.ps1 b/install-as-dotnet-tool.ps1 index b26f6ee..32f1313 100644 --- a/install-as-dotnet-tool.ps1 +++ b/install-as-dotnet-tool.ps1 @@ -1,4 +1,16 @@ -dotnet pack ./src -o ./nupkg +#!/usr/bin/env pwsh + +function Test-CommandExists([Parameter(Mandatory)] [string] $command) +{ + try { + if (Get-Command $command -ErrorAction Stop) { return $true } + } catch { + return $false + } +} + + +dotnet pack ./src -o ./nupkg $exists = $(Test-CommandExists script-launcher) $action = $exists ? 'update' : 'install' From 5baff758ea6f9a32dc028c7fab9a41a494ed180e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 02:20:22 +0000 Subject: [PATCH 049/100] NuGet(deps): Bump Spectre.Console from 0.44.0 to 0.45.0 in /src Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.44.0 to 0.45.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.44.0...0.45.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 313ec18..df35267 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From 82debdb93a353ff180fc68db962c198463561f13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 02:20:22 +0000 Subject: [PATCH 050/100] NuGet(deps): Bump Spectre.Console from 0.44.0 to 0.45.0 in /src Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.44.0 to 0.45.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.44.0...0.45.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 313ec18..df35267 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From 2c02090a25a7c77707551bdd3a9ef7c83777d35b Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 15 Oct 2022 12:24:30 +0200 Subject: [PATCH 051/100] Feat: Switch to `Spectre.Console.Cli` as CLI framework (#3) --- src/Program.cs | 129 ++++++++++++++++++++++++-------------- src/ScriptLauncher.csproj | 4 +- 2 files changed, 84 insertions(+), 49 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 5f197e0..c17fdbf 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,58 +1,93 @@ -using Cocona; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Spectre.Console; +using Spectre.Console.Cli; -var app = CoconaLiteApp.Create(); +var app = new CommandApp(); +app.Configure(x => x.SetApplicationName("script-launcher")); +return app.Run(args); -app.AddCommand(RootCommand); -app.Run(); - -static async Task RootCommand( - [Option("extensions", new[] { 'x' }, Description = "Comma separated list of script extensions")] string? extensions, - [Option("depth", new[] { 'd' }, Description = "Search depth")] int depth = 1, - [Option("elevated", new[] { 'e' }, Description = "Run with elevated privileges")] bool elevated = false, - [Option("group", new[] { 'g' }, Description = "Group scripts by folder")] bool group = false, - [Option("brief", new[] { 'b' }, Description = "Show brief information")] bool brief = false, - [Argument(Name = "Directory", Description = "Starting directory")] string directory = ".") +sealed class RootCommand : AsyncCommand { - if (!Directory.Exists(directory)) + private const int Failure = 1; + private const int Success = 0; + + public override async Task ExecuteAsync( + [NotNull] CommandContext context, + [NotNull] RootCommandSettings settings + ) { - AnsiConsole.Markup($"[red]The directory '{directory}' does not exist.[/]"); - Environment.ExitCode = 1; - return; - } - - FileInfo[] files; - var finder = new ScriptFinder(extensions, directory, depth); - - if (group) - { - var dict = finder.GetScriptsByDirectory(); - - if (dict.Count == 0) + if (!Directory.Exists(settings.Directory)) { - AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = 1; - return; + AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); + // Environment.ExitCode = 1; + return Failure; } - var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(dirPrompt); - files = dict[directoryInfo]; - } - else - { - files = finder.GetScripts(); - } + FileInfo[] files; + var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); - if (files.Length == 0) - { - AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = 1; - return; + if (settings.Group) + { + var dict = finder.GetScriptsByDirectory(); + + if (dict.Count == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); + files = dict[directoryInfo]; + } + else + { + files = finder.GetScripts(); + } + + if (files.Length == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); + var scripts = AnsiConsole.Prompt(prompt); + + await ScriptExecutor.ExecAsync(scripts, settings.Elevated); + + return Success; } - - var prompt = PromptConstructor.GetScriptPrompt(files, brief); - var scripts = AnsiConsole.Prompt(prompt); - - await ScriptExecutor.ExecAsync(scripts, elevated); +} + +internal class RootCommandSettings : CommandSettings +{ + [Description("Comma separated list of script extensions")] + [CommandOption("-x|--extensions")] + public string? Extensions { get; init; } + + [Description("Search depth")] + [CommandOption("-d|--depth")] + public int Depth { get; init; } = 1; + + [Description("Run with elevated privileges")] + [CommandOption("-e|--elevated")] + public bool Elevated { get; init; } = false; + + [Description("Group scripts by folder")] + [CommandOption("-g|--group")] + public bool Group { get; init; } = false; + + [Description("Show brief information")] + [CommandOption("-b|--brief")] + public bool Brief { get; init; } = false; + + [Description("Starting directory (Default: .)")] + [CommandArgument(0, "")] + public string Directory { get; init; } = "."; } diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index df35267..1e68697 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,14 +5,14 @@ net6.0 enable enable - 0.1.0 + 0.1.1 true script-launcher - + From 1cf2e05a6706e3e2cde28a557e9e2a7dcf314ecf Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 15 Oct 2022 12:24:30 +0200 Subject: [PATCH 052/100] Feat: Switch to `Spectre.Console.Cli` as CLI framework (#3) --- src/Program.cs | 129 ++++++++++++++++++++++++-------------- src/ScriptLauncher.csproj | 4 +- 2 files changed, 84 insertions(+), 49 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 5f197e0..c17fdbf 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,58 +1,93 @@ -using Cocona; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Spectre.Console; +using Spectre.Console.Cli; -var app = CoconaLiteApp.Create(); +var app = new CommandApp(); +app.Configure(x => x.SetApplicationName("script-launcher")); +return app.Run(args); -app.AddCommand(RootCommand); -app.Run(); - -static async Task RootCommand( - [Option("extensions", new[] { 'x' }, Description = "Comma separated list of script extensions")] string? extensions, - [Option("depth", new[] { 'd' }, Description = "Search depth")] int depth = 1, - [Option("elevated", new[] { 'e' }, Description = "Run with elevated privileges")] bool elevated = false, - [Option("group", new[] { 'g' }, Description = "Group scripts by folder")] bool group = false, - [Option("brief", new[] { 'b' }, Description = "Show brief information")] bool brief = false, - [Argument(Name = "Directory", Description = "Starting directory")] string directory = ".") +sealed class RootCommand : AsyncCommand { - if (!Directory.Exists(directory)) + private const int Failure = 1; + private const int Success = 0; + + public override async Task ExecuteAsync( + [NotNull] CommandContext context, + [NotNull] RootCommandSettings settings + ) { - AnsiConsole.Markup($"[red]The directory '{directory}' does not exist.[/]"); - Environment.ExitCode = 1; - return; - } - - FileInfo[] files; - var finder = new ScriptFinder(extensions, directory, depth); - - if (group) - { - var dict = finder.GetScriptsByDirectory(); - - if (dict.Count == 0) + if (!Directory.Exists(settings.Directory)) { - AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = 1; - return; + AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); + // Environment.ExitCode = 1; + return Failure; } - var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(dirPrompt); - files = dict[directoryInfo]; - } - else - { - files = finder.GetScripts(); - } + FileInfo[] files; + var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); - if (files.Length == 0) - { - AnsiConsole.Markup($"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]"); - Environment.ExitCode = 1; - return; + if (settings.Group) + { + var dict = finder.GetScriptsByDirectory(); + + if (dict.Count == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); + files = dict[directoryInfo]; + } + else + { + files = finder.GetScripts(); + } + + if (files.Length == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); + var scripts = AnsiConsole.Prompt(prompt); + + await ScriptExecutor.ExecAsync(scripts, settings.Elevated); + + return Success; } - - var prompt = PromptConstructor.GetScriptPrompt(files, brief); - var scripts = AnsiConsole.Prompt(prompt); - - await ScriptExecutor.ExecAsync(scripts, elevated); +} + +internal class RootCommandSettings : CommandSettings +{ + [Description("Comma separated list of script extensions")] + [CommandOption("-x|--extensions")] + public string? Extensions { get; init; } + + [Description("Search depth")] + [CommandOption("-d|--depth")] + public int Depth { get; init; } = 1; + + [Description("Run with elevated privileges")] + [CommandOption("-e|--elevated")] + public bool Elevated { get; init; } = false; + + [Description("Group scripts by folder")] + [CommandOption("-g|--group")] + public bool Group { get; init; } = false; + + [Description("Show brief information")] + [CommandOption("-b|--brief")] + public bool Brief { get; init; } = false; + + [Description("Starting directory (Default: .)")] + [CommandArgument(0, "")] + public string Directory { get; init; } = "."; } diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index df35267..1e68697 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,14 +5,14 @@ net6.0 enable enable - 0.1.0 + 0.1.1 true script-launcher - + From 6a8e0b9a91a911154d872a3ec1a945d05f64a1c9 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 15 Oct 2022 13:06:19 +0200 Subject: [PATCH 053/100] chore: update `README.md` --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 428a902..2089197 100644 --- a/README.md +++ b/README.md @@ -32,19 +32,19 @@ _**NOTE**_: The option `-p:PublishTrimmed=true` may produce some *warnings*. If ## Usage ```sh -Usage: script-launcher [--extensions ] [--depth ] [--elevated] [--group] [--brief] [--help] [--version] directory +USAGE: + script-launcher [OPTIONS] -Arguments: - 0: directory Starting directory (Default: .) +ARGUMENTS: + Starting directory (Default: .) -Options: - -x, --extensions Comma separated list of script extensions - -d, --depth Search depth (Default: 1) - -e, --elevated Run with elevated privileges - -g, --group Group scripts by folder - -b, --brief Show brief information - -h, --help Show help message - --version Show version +OPTIONS: + -h, --help Prints help information + -x, --extensions Comma separated list of script extensions + -d, --depth Search depth + -e, --elevated Run with elevated privileges + -g, --group Group scripts by folder + -b, --brief Show brief information ``` [CLI]: https://docs.microsoft.com/en-us/dotnet/core/tools/ ".NET CLI Docs" From 128d9286acb2b45fcf6882b7182d93d769688b96 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sat, 15 Oct 2022 13:06:19 +0200 Subject: [PATCH 054/100] chore: update `README.md` --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 428a902..2089197 100644 --- a/README.md +++ b/README.md @@ -32,19 +32,19 @@ _**NOTE**_: The option `-p:PublishTrimmed=true` may produce some *warnings*. If ## Usage ```sh -Usage: script-launcher [--extensions ] [--depth ] [--elevated] [--group] [--brief] [--help] [--version] directory +USAGE: + script-launcher [OPTIONS] -Arguments: - 0: directory Starting directory (Default: .) +ARGUMENTS: + Starting directory (Default: .) -Options: - -x, --extensions Comma separated list of script extensions - -d, --depth Search depth (Default: 1) - -e, --elevated Run with elevated privileges - -g, --group Group scripts by folder - -b, --brief Show brief information - -h, --help Show help message - --version Show version +OPTIONS: + -h, --help Prints help information + -x, --extensions Comma separated list of script extensions + -d, --depth Search depth + -e, --elevated Run with elevated privileges + -g, --group Group scripts by folder + -b, --brief Show brief information ``` [CLI]: https://docs.microsoft.com/en-us/dotnet/core/tools/ ".NET CLI Docs" From b75ad45aaa530f25869bf59f3c56c1c34f300feb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 02:36:14 +0000 Subject: [PATCH 055/100] NuGet(deps): Bump Spectre.Console.Cli from 0.45.0 to 0.46.0 in /src Bumps [Spectre.Console.Cli](https://github.com/spectreconsole/spectre.console) from 0.45.0 to 0.46.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.45.0...0.46.0) --- updated-dependencies: - dependency-name: Spectre.Console.Cli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 1e68697..6607f28 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From d10340a644d1602da531238336814421568042d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 09:07:47 +0100 Subject: [PATCH 056/100] NuGet(deps): Bump Spectre.Console from 0.45.0 to 0.46.0 in /src (#5) Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.45.0 to 0.46.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.45.0...0.46.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marcello Lamonaca --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 6607f28..0914938 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -11,7 +11,7 @@ - + From 38eb93354fb31de1d3768f021e0daeabbe692bc5 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 27 Jan 2023 16:27:08 +0100 Subject: [PATCH 057/100] feat: rename executable to `scrl` --- README.md | 2 +- install-as-dotnet-tool.ps1 | 4 ++-- install-as-dotnet-tool.sh | 2 +- src/Program.cs | 2 +- src/ScriptLauncher.csproj | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2089197..ab7118b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ _**NOTE**_: The option `-p:PublishTrimmed=true` may produce some *warnings*. If ```sh USAGE: - script-launcher [OPTIONS] + scrl [OPTIONS] ARGUMENTS: Starting directory (Default: .) diff --git a/install-as-dotnet-tool.ps1 b/install-as-dotnet-tool.ps1 index 32f1313..7d5076a 100644 --- a/install-as-dotnet-tool.ps1 +++ b/install-as-dotnet-tool.ps1 @@ -1,4 +1,4 @@ -#!/usr/bin/env pwsh +#!/usr/bin/env pwsh function Test-CommandExists([Parameter(Mandatory)] [string] $command) { @@ -12,7 +12,7 @@ function Test-CommandExists([Parameter(Mandatory)] [string] $command) dotnet pack ./src -o ./nupkg -$exists = $(Test-CommandExists script-launcher) +$exists = $(Test-CommandExists scrl) $action = $exists ? 'update' : 'install' dotnet tool $action -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources diff --git a/install-as-dotnet-tool.sh b/install-as-dotnet-tool.sh index 4b0924f..0232500 100755 --- a/install-as-dotnet-tool.sh +++ b/install-as-dotnet-tool.sh @@ -2,7 +2,7 @@ dotnet pack ./src -o ./nupkg -EXISTS=$(command -v script-launcher) +EXISTS=$(command -v scrl) if [ "$EXISTS" ]; then ACTION="update" diff --git a/src/Program.cs b/src/Program.cs index c17fdbf..a935a1d 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -4,7 +4,7 @@ using Spectre.Console; using Spectre.Console.Cli; var app = new CommandApp(); -app.Configure(x => x.SetApplicationName("script-launcher")); +app.Configure(x => x.SetApplicationName("scrl")); return app.Run(args); sealed class RootCommand : AsyncCommand diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 0914938..1019ff8 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,9 +5,9 @@ net6.0 enable enable - 0.1.1 + 0.1.2 true - script-launcher + scrl From 5ffbbd4ba9af5f25729b7a88d3ced222269cf606 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 22 Mar 2023 15:25:02 +0100 Subject: [PATCH 058/100] Create .github/workflows/codeql.yml --- .github/workflows/codeql.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..67cdc2a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: "CodeQL Analysis" + +on: + push: + branches: [ "main", "develop" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '00 18 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From 0642e9d4ef3d3b11350b55ef720560456f16a57e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 02:36:14 +0000 Subject: [PATCH 059/100] NuGet(deps): Bump Spectre.Console.Cli from 0.45.0 to 0.46.0 in /src Bumps [Spectre.Console.Cli](https://github.com/spectreconsole/spectre.console) from 0.45.0 to 0.46.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.45.0...0.46.0) --- updated-dependencies: - dependency-name: Spectre.Console.Cli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 1e68697..6607f28 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From 0f91b2202171916194740b2768623d08a86034c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 09:07:47 +0100 Subject: [PATCH 060/100] NuGet(deps): Bump Spectre.Console from 0.45.0 to 0.46.0 in /src (#5) Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.45.0 to 0.46.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.45.0...0.46.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marcello Lamonaca --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 6607f28..0914938 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -11,7 +11,7 @@ - + From cf3f670fbf9bd3434037e09b047ecf626ae84d56 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 27 Jan 2023 16:27:08 +0100 Subject: [PATCH 061/100] feat: rename executable to `scrl` --- README.md | 2 +- install-as-dotnet-tool.ps1 | 4 ++-- install-as-dotnet-tool.sh | 2 +- src/Program.cs | 2 +- src/ScriptLauncher.csproj | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2089197..ab7118b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ _**NOTE**_: The option `-p:PublishTrimmed=true` may produce some *warnings*. If ```sh USAGE: - script-launcher [OPTIONS] + scrl [OPTIONS] ARGUMENTS: Starting directory (Default: .) diff --git a/install-as-dotnet-tool.ps1 b/install-as-dotnet-tool.ps1 index 32f1313..7d5076a 100644 --- a/install-as-dotnet-tool.ps1 +++ b/install-as-dotnet-tool.ps1 @@ -1,4 +1,4 @@ -#!/usr/bin/env pwsh +#!/usr/bin/env pwsh function Test-CommandExists([Parameter(Mandatory)] [string] $command) { @@ -12,7 +12,7 @@ function Test-CommandExists([Parameter(Mandatory)] [string] $command) dotnet pack ./src -o ./nupkg -$exists = $(Test-CommandExists script-launcher) +$exists = $(Test-CommandExists scrl) $action = $exists ? 'update' : 'install' dotnet tool $action -g ScriptLauncher --add-source ./nupkg --ignore-failed-sources diff --git a/install-as-dotnet-tool.sh b/install-as-dotnet-tool.sh index 4b0924f..0232500 100755 --- a/install-as-dotnet-tool.sh +++ b/install-as-dotnet-tool.sh @@ -2,7 +2,7 @@ dotnet pack ./src -o ./nupkg -EXISTS=$(command -v script-launcher) +EXISTS=$(command -v scrl) if [ "$EXISTS" ]; then ACTION="update" diff --git a/src/Program.cs b/src/Program.cs index c17fdbf..a935a1d 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -4,7 +4,7 @@ using Spectre.Console; using Spectre.Console.Cli; var app = new CommandApp(); -app.Configure(x => x.SetApplicationName("script-launcher")); +app.Configure(x => x.SetApplicationName("scrl")); return app.Run(args); sealed class RootCommand : AsyncCommand diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 0914938..1019ff8 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,9 +5,9 @@ net6.0 enable enable - 0.1.1 + 0.1.2 true - script-launcher + scrl From 0625ed62518e0bf7bf8d0cd2ac4c4693eb8d494f Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 22 Mar 2023 15:25:02 +0100 Subject: [PATCH 062/100] Create .github/workflows/codeql.yml --- .github/workflows/codeql.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..67cdc2a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: "CodeQL Analysis" + +on: + push: + branches: [ "main", "develop" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '00 18 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From 060f2f02235822d7fcd3bb7b7bb802959b3c9cf0 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 19 May 2023 19:46:08 +0200 Subject: [PATCH 063/100] fix: script ptompt message --- src/PromptConstructor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index ba42adc..e01d3fa 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -28,7 +28,7 @@ static class PromptConstructor public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) { var prompt = new MultiSelectionPrompt() - .Title("Select a script the scripts to execute:") + .Title("Select the scripts to execute:") .NotRequired() .PageSize(ScriptListSize) .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") From 504bed4e5054b385fc7ddcb326582a107fc744a2 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 19 May 2023 19:46:08 +0200 Subject: [PATCH 064/100] fix: script ptompt message --- src/PromptConstructor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index ba42adc..e01d3fa 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -28,7 +28,7 @@ static class PromptConstructor public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) { var prompt = new MultiSelectionPrompt() - .Title("Select a script the scripts to execute:") + .Title("Select the scripts to execute:") .NotRequired() .PageSize(ScriptListSize) .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") From c2a3b1e35d02c8b033db85dad64c3b0776c02c5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:54:45 +0200 Subject: [PATCH 065/100] NuGet(deps): Bump Spectre.Console from 0.46.0 to 0.47.0 in /src (#7) Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.46.0 to 0.47.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.46.0...0.47.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 1019ff8..d3a3489 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -11,7 +11,7 @@ - + From 40c2508b9430e943f8f2191a96eeeb410a748aa6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:54:45 +0200 Subject: [PATCH 066/100] NuGet(deps): Bump Spectre.Console from 0.46.0 to 0.47.0 in /src (#7) Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.46.0 to 0.47.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.46.0...0.47.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 1019ff8..d3a3489 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -11,7 +11,7 @@ - + From ac8c48c5bb25506c63fbaac17390d3bcebec2337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:57:46 +0200 Subject: [PATCH 067/100] NuGet(deps): Bump Spectre.Console.Cli from 0.46.0 to 0.47.0 in /src (#6) Bumps [Spectre.Console.Cli](https://github.com/spectreconsole/spectre.console) from 0.46.0 to 0.47.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.46.0...0.47.0) --- updated-dependencies: - dependency-name: Spectre.Console.Cli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index d3a3489..5d6b43e 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From 0f7d7d2ff84a2ad8f21a0f61a2f11ce4c4850eee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:57:46 +0200 Subject: [PATCH 068/100] NuGet(deps): Bump Spectre.Console.Cli from 0.46.0 to 0.47.0 in /src (#6) Bumps [Spectre.Console.Cli](https://github.com/spectreconsole/spectre.console) from 0.46.0 to 0.47.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.46.0...0.47.0) --- updated-dependencies: - dependency-name: Spectre.Console.Cli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index d3a3489..5d6b43e 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -12,7 +12,7 @@ - + From 55a240e015c64c0bb2d02cdcea6447b61da53bf6 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 26 May 2023 16:28:48 +0200 Subject: [PATCH 069/100] chore: refactor codebase - add namespace - move commands to dedicated file - collapse linux shells to single process info --- src/Commands.cs | 98 ++++++++++++++++++++++++++++++++++++++++ src/Program.cs | 89 +----------------------------------- src/PromptConstructor.cs | 37 ++++++++------- src/ScriptExecutor.cs | 90 ++++++++++++++---------------------- src/ScriptFinder.cs | 24 +++++----- 5 files changed, 166 insertions(+), 172 deletions(-) create mode 100644 src/Commands.cs diff --git a/src/Commands.cs b/src/Commands.cs new file mode 100644 index 0000000..5337cc6 --- /dev/null +++ b/src/Commands.cs @@ -0,0 +1,98 @@ +using System.ComponentModel; + +using Spectre.Console; +using Spectre.Console.Cli; + +namespace ScriptLauncher; + +internal sealed class RootCommand : AsyncCommand + { + private const int Failure = 1; + private const int Success = 0; + + public override async Task ExecuteAsync( + CommandContext context, + RootCommandSettings settings + ) + { + if (!Directory.Exists(settings.Directory)) + { + AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); + return Failure; + } + + FileInfo[] files; + var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); + + if (settings.Group) + { + var dict = finder.GetScriptsByDirectory(); + + if (dict.Count == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); + files = dict[directoryInfo]; + } + else + { + files = finder.GetScripts(); + } + + if (files.Length == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); + var scripts = AnsiConsole.Prompt(prompt); + + try + { + await ScriptExecutor.ExecAsync(scripts, settings.Elevated); + } + catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + return Failure; + } + + return Success; + } + } + + internal class RootCommandSettings : CommandSettings + { + [Description("Comma separated list of script extensions")] + [CommandOption("-x|--extensions")] + public string? Extensions { get; init; } + + [Description("Search depth")] + [CommandOption("-d|--depth")] + public int Depth { get; init; } = 1; + + [Description("Run with elevated privileges")] + [CommandOption("-e|--elevated")] + public bool Elevated { get; init; } = false; + + [Description("Group scripts by folder")] + [CommandOption("-g|--group")] + public bool Group { get; init; } = false; + + [Description("Show brief information")] + [CommandOption("-b|--brief")] + public bool Brief { get; init; } = false; + + [Description("Starting directory (Default: .)")] + [CommandArgument(0, "")] + public string Directory { get; init; } = "."; + } diff --git a/src/Program.cs b/src/Program.cs index a935a1d..f53f375 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,93 +1,6 @@ -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using Spectre.Console; +using ScriptLauncher; using Spectre.Console.Cli; var app = new CommandApp(); app.Configure(x => x.SetApplicationName("scrl")); return app.Run(args); - -sealed class RootCommand : AsyncCommand -{ - private const int Failure = 1; - private const int Success = 0; - - public override async Task ExecuteAsync( - [NotNull] CommandContext context, - [NotNull] RootCommandSettings settings - ) - { - if (!Directory.Exists(settings.Directory)) - { - AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); - // Environment.ExitCode = 1; - return Failure; - } - - FileInfo[] files; - var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); - - if (settings.Group) - { - var dict = finder.GetScriptsByDirectory(); - - if (dict.Count == 0) - { - AnsiConsole.Markup( - $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" - ); - return Failure; - } - - var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(dirPrompt); - files = dict[directoryInfo]; - } - else - { - files = finder.GetScripts(); - } - - if (files.Length == 0) - { - AnsiConsole.Markup( - $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" - ); - return Failure; - } - - var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); - var scripts = AnsiConsole.Prompt(prompt); - - await ScriptExecutor.ExecAsync(scripts, settings.Elevated); - - return Success; - } -} - -internal class RootCommandSettings : CommandSettings -{ - [Description("Comma separated list of script extensions")] - [CommandOption("-x|--extensions")] - public string? Extensions { get; init; } - - [Description("Search depth")] - [CommandOption("-d|--depth")] - public int Depth { get; init; } = 1; - - [Description("Run with elevated privileges")] - [CommandOption("-e|--elevated")] - public bool Elevated { get; init; } = false; - - [Description("Group scripts by folder")] - [CommandOption("-g|--group")] - public bool Group { get; init; } = false; - - [Description("Show brief information")] - [CommandOption("-b|--brief")] - public bool Brief { get; init; } = false; - - [Description("Starting directory (Default: .)")] - [CommandArgument(0, "")] - public string Directory { get; init; } = "."; -} diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index e01d3fa..8c0952e 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -1,11 +1,14 @@ using System.Text; using Spectre.Console; -static class PromptConstructor +namespace ScriptLauncher; + +internal static class PromptConstructor { const int ScriptListSize = 15; - private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + private static Style SelectionHighlight => + new(decoration: Decoration.Bold | Decoration.Underline); private static string FileStyle(FileInfo info, bool brief) { @@ -28,14 +31,16 @@ static class PromptConstructor public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) { var prompt = new MultiSelectionPrompt() - .Title("Select the scripts to execute:") - .NotRequired() - .PageSize(ScriptListSize) - .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => FileStyle(x, brief)) - .HighlightStyle(SelectionHighlight) - .AddChoices(files); + .Title("Select the scripts to execute:") + .NotRequired() + .PageSize(ScriptListSize) + .InstructionsText( + "[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]" + ) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(x => FileStyle(x, brief)) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); return prompt; } @@ -43,12 +48,12 @@ static class PromptConstructor public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) { var prompt = new SelectionPrompt() - .Title("Select a directory:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(DirectoryStyle) - .HighlightStyle(SelectionHighlight) - .AddChoices(directories); + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(DirectoryStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(directories); return prompt; } diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index a80e9b3..494abf2 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -1,70 +1,46 @@ -using System.ComponentModel; using System.Diagnostics; -using Spectre.Console; -static class ScriptExecutor +namespace ScriptLauncher; + +internal static class ScriptExecutor { - public static async Task ExecAsync(List files, bool elevated) - { + public static async Task ExecAsync(List files, bool elevated) => await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); - } - public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + private static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) { var process = GetExecutableProcessInfo(file, elevated); - - if (process is null) return; - - try + if (process is null) { - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); - } - catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) - { - AnsiConsole.Markup($"[red]{ex.Message}[/]"); + return; } + + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); } - private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) => file.Extension switch { - return file.Extension switch + ".bat" or ".cmd" => new ProcessStartInfo { - ".bat" or ".cmd" => new ProcessStartInfo - { - FileName = "cmd", - Arguments = $"/Q /C .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".ps1" => new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".sh" => new ProcessStartInfo - { - FileName = "bash", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".zsh" => new ProcessStartInfo - { - FileName = "zsh", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".fish" => new ProcessStartInfo - { - FileName = "fish", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - _ => null - }; - } -} + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" or ".zsh" or ".fish" => new ProcessStartInfo + { + FileName = "sh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + var _ => null + }; +} \ No newline at end of file diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs index 58c512a..375ca4f 100644 --- a/src/ScriptFinder.cs +++ b/src/ScriptFinder.cs @@ -1,6 +1,6 @@ -using Spectre.Console; +namespace ScriptLauncher; -readonly struct ScriptFinder +internal readonly struct ScriptFinder { static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; public string[] Extensions { get; } @@ -10,10 +10,12 @@ readonly struct ScriptFinder public ScriptFinder(string? extensions, string directory, int depth) { - Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .ToHashSet() - .Select(x => $".{x.TrimStart('.')}") - .ToArray() ?? DefaultExtensions; + Extensions = + extensions + ?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .Select(x => $".{x.TrimStart('.')}") + .ToArray() ?? DefaultExtensions; Depth = depth; RootDirectory = directory; @@ -44,9 +46,9 @@ readonly struct ScriptFinder public IDictionary GetScriptsByDirectory() => Extensions - .Select(GetScriptFilesWithExtension) - .SelectMany(x => x) - .GroupBy(x => x.DirectoryName!) - .OrderBy(x => x.Key) - .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); + .Select(GetScriptFilesWithExtension) + .SelectMany(x => x) + .GroupBy(x => x.DirectoryName!) + .OrderBy(x => x.Key) + .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); } From c54e96e3e3c40e4fe863742dcd87d266d4cc34d7 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 26 May 2023 16:28:48 +0200 Subject: [PATCH 070/100] chore: refactor codebase - add namespace - move commands to dedicated file - collapse linux shells to single process info --- src/Commands.cs | 98 ++++++++++++++++++++++++++++++++++++++++ src/Program.cs | 89 +----------------------------------- src/PromptConstructor.cs | 37 ++++++++------- src/ScriptExecutor.cs | 90 ++++++++++++++---------------------- src/ScriptFinder.cs | 24 +++++----- 5 files changed, 166 insertions(+), 172 deletions(-) create mode 100644 src/Commands.cs diff --git a/src/Commands.cs b/src/Commands.cs new file mode 100644 index 0000000..5337cc6 --- /dev/null +++ b/src/Commands.cs @@ -0,0 +1,98 @@ +using System.ComponentModel; + +using Spectre.Console; +using Spectre.Console.Cli; + +namespace ScriptLauncher; + +internal sealed class RootCommand : AsyncCommand + { + private const int Failure = 1; + private const int Success = 0; + + public override async Task ExecuteAsync( + CommandContext context, + RootCommandSettings settings + ) + { + if (!Directory.Exists(settings.Directory)) + { + AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); + return Failure; + } + + FileInfo[] files; + var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); + + if (settings.Group) + { + var dict = finder.GetScriptsByDirectory(); + + if (dict.Count == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); + files = dict[directoryInfo]; + } + else + { + files = finder.GetScripts(); + } + + if (files.Length == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); + var scripts = AnsiConsole.Prompt(prompt); + + try + { + await ScriptExecutor.ExecAsync(scripts, settings.Elevated); + } + catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + return Failure; + } + + return Success; + } + } + + internal class RootCommandSettings : CommandSettings + { + [Description("Comma separated list of script extensions")] + [CommandOption("-x|--extensions")] + public string? Extensions { get; init; } + + [Description("Search depth")] + [CommandOption("-d|--depth")] + public int Depth { get; init; } = 1; + + [Description("Run with elevated privileges")] + [CommandOption("-e|--elevated")] + public bool Elevated { get; init; } = false; + + [Description("Group scripts by folder")] + [CommandOption("-g|--group")] + public bool Group { get; init; } = false; + + [Description("Show brief information")] + [CommandOption("-b|--brief")] + public bool Brief { get; init; } = false; + + [Description("Starting directory (Default: .)")] + [CommandArgument(0, "")] + public string Directory { get; init; } = "."; + } diff --git a/src/Program.cs b/src/Program.cs index a935a1d..f53f375 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,93 +1,6 @@ -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using Spectre.Console; +using ScriptLauncher; using Spectre.Console.Cli; var app = new CommandApp(); app.Configure(x => x.SetApplicationName("scrl")); return app.Run(args); - -sealed class RootCommand : AsyncCommand -{ - private const int Failure = 1; - private const int Success = 0; - - public override async Task ExecuteAsync( - [NotNull] CommandContext context, - [NotNull] RootCommandSettings settings - ) - { - if (!Directory.Exists(settings.Directory)) - { - AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); - // Environment.ExitCode = 1; - return Failure; - } - - FileInfo[] files; - var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); - - if (settings.Group) - { - var dict = finder.GetScriptsByDirectory(); - - if (dict.Count == 0) - { - AnsiConsole.Markup( - $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" - ); - return Failure; - } - - var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(dirPrompt); - files = dict[directoryInfo]; - } - else - { - files = finder.GetScripts(); - } - - if (files.Length == 0) - { - AnsiConsole.Markup( - $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" - ); - return Failure; - } - - var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); - var scripts = AnsiConsole.Prompt(prompt); - - await ScriptExecutor.ExecAsync(scripts, settings.Elevated); - - return Success; - } -} - -internal class RootCommandSettings : CommandSettings -{ - [Description("Comma separated list of script extensions")] - [CommandOption("-x|--extensions")] - public string? Extensions { get; init; } - - [Description("Search depth")] - [CommandOption("-d|--depth")] - public int Depth { get; init; } = 1; - - [Description("Run with elevated privileges")] - [CommandOption("-e|--elevated")] - public bool Elevated { get; init; } = false; - - [Description("Group scripts by folder")] - [CommandOption("-g|--group")] - public bool Group { get; init; } = false; - - [Description("Show brief information")] - [CommandOption("-b|--brief")] - public bool Brief { get; init; } = false; - - [Description("Starting directory (Default: .)")] - [CommandArgument(0, "")] - public string Directory { get; init; } = "."; -} diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index e01d3fa..8c0952e 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -1,11 +1,14 @@ using System.Text; using Spectre.Console; -static class PromptConstructor +namespace ScriptLauncher; + +internal static class PromptConstructor { const int ScriptListSize = 15; - private static Style SelectionHighlight => new(decoration: Decoration.Bold | Decoration.Underline); + private static Style SelectionHighlight => + new(decoration: Decoration.Bold | Decoration.Underline); private static string FileStyle(FileInfo info, bool brief) { @@ -28,14 +31,16 @@ static class PromptConstructor public static MultiSelectionPrompt GetScriptPrompt(FileInfo[] files, bool brief) { var prompt = new MultiSelectionPrompt() - .Title("Select the scripts to execute:") - .NotRequired() - .PageSize(ScriptListSize) - .InstructionsText("[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]") - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(x => FileStyle(x, brief)) - .HighlightStyle(SelectionHighlight) - .AddChoices(files); + .Title("Select the scripts to execute:") + .NotRequired() + .PageSize(ScriptListSize) + .InstructionsText( + "[grey](Press [blue][/] to toggle a script, [green][/] to accept)[/]" + ) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(x => FileStyle(x, brief)) + .HighlightStyle(SelectionHighlight) + .AddChoices(files); return prompt; } @@ -43,12 +48,12 @@ static class PromptConstructor public static SelectionPrompt GetDirectoryPrompt(DirectoryInfo[] directories) { var prompt = new SelectionPrompt() - .Title("Select a directory:") - .PageSize(ScriptListSize) - .MoreChoicesText("[grey]Move up and down to reveal more options[/]") - .UseConverter(DirectoryStyle) - .HighlightStyle(SelectionHighlight) - .AddChoices(directories); + .Title("Select a directory:") + .PageSize(ScriptListSize) + .MoreChoicesText("[grey]Move up and down to reveal more options[/]") + .UseConverter(DirectoryStyle) + .HighlightStyle(SelectionHighlight) + .AddChoices(directories); return prompt; } diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index a80e9b3..494abf2 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -1,70 +1,46 @@ -using System.ComponentModel; using System.Diagnostics; -using Spectre.Console; -static class ScriptExecutor +namespace ScriptLauncher; + +internal static class ScriptExecutor { - public static async Task ExecAsync(List files, bool elevated) - { + public static async Task ExecAsync(List files, bool elevated) => await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); - } - public static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + private static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) { var process = GetExecutableProcessInfo(file, elevated); - - if (process is null) return; - - try + if (process is null) { - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); - } - catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) - { - AnsiConsole.Markup($"[red]{ex.Message}[/]"); + return; } + + await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); } - private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) => file.Extension switch { - return file.Extension switch + ".bat" or ".cmd" => new ProcessStartInfo { - ".bat" or ".cmd" => new ProcessStartInfo - { - FileName = "cmd", - Arguments = $"/Q /C .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".ps1" => new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".sh" => new ProcessStartInfo - { - FileName = "bash", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".zsh" => new ProcessStartInfo - { - FileName = "zsh", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".fish" => new ProcessStartInfo - { - FileName = "fish", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - _ => null - }; - } -} + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" or ".zsh" or ".fish" => new ProcessStartInfo + { + FileName = "sh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + var _ => null + }; +} \ No newline at end of file diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs index 58c512a..375ca4f 100644 --- a/src/ScriptFinder.cs +++ b/src/ScriptFinder.cs @@ -1,6 +1,6 @@ -using Spectre.Console; +namespace ScriptLauncher; -readonly struct ScriptFinder +internal readonly struct ScriptFinder { static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; public string[] Extensions { get; } @@ -10,10 +10,12 @@ readonly struct ScriptFinder public ScriptFinder(string? extensions, string directory, int depth) { - Extensions = extensions?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .ToHashSet() - .Select(x => $".{x.TrimStart('.')}") - .ToArray() ?? DefaultExtensions; + Extensions = + extensions + ?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToHashSet() + .Select(x => $".{x.TrimStart('.')}") + .ToArray() ?? DefaultExtensions; Depth = depth; RootDirectory = directory; @@ -44,9 +46,9 @@ readonly struct ScriptFinder public IDictionary GetScriptsByDirectory() => Extensions - .Select(GetScriptFilesWithExtension) - .SelectMany(x => x) - .GroupBy(x => x.DirectoryName!) - .OrderBy(x => x.Key) - .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); + .Select(GetScriptFilesWithExtension) + .SelectMany(x => x) + .GroupBy(x => x.DirectoryName!) + .OrderBy(x => x.Key) + .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); } From 191d4eb9f5dcf53bc924407d6daef71ac5207a15 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 26 May 2023 16:30:03 +0200 Subject: [PATCH 071/100] fix: start powershell without user profile --- src/ScriptExecutor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index 494abf2..2038f5c 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -30,7 +30,7 @@ internal static class ScriptExecutor ".ps1" => new ProcessStartInfo { FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -File .\\{file.Name}", Verb = elevated ? "runas /user:Administrator" : string.Empty, WorkingDirectory = file.DirectoryName }, From 6d98eb63232c4c3184ccf15a77fcb3117892ceff Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 26 May 2023 16:30:03 +0200 Subject: [PATCH 072/100] fix: start powershell without user profile --- src/ScriptExecutor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index 494abf2..2038f5c 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -30,7 +30,7 @@ internal static class ScriptExecutor ".ps1" => new ProcessStartInfo { FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File .\\{file.Name}", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -File .\\{file.Name}", Verb = elevated ? "runas /user:Administrator" : string.Empty, WorkingDirectory = file.DirectoryName }, From 59745bcb5d4b76dec18f0a0f07226c6806b14f15 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 26 May 2023 16:31:59 +0200 Subject: [PATCH 073/100] chore: bump version - include README in package - add package description --- src/ScriptLauncher.csproj | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 5d6b43e..c293f25 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,8 +5,10 @@ net6.0 enable enable - 0.1.2 + 0.1.3 true + Tool to find and exec shell scripts + README.md scrl @@ -15,4 +17,8 @@ + + + + From d6c8f5550982286cd2e1c32a7c031f896ff2986b Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 26 May 2023 16:31:59 +0200 Subject: [PATCH 074/100] chore: bump version - include README in package - add package description --- src/ScriptLauncher.csproj | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 5d6b43e..c293f25 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,8 +5,10 @@ net6.0 enable enable - 0.1.2 + 0.1.3 true + Tool to find and exec shell scripts + README.md scrl @@ -15,4 +17,8 @@ + + + + From bd0147e7d8377461e88d3b25758c02814044a645 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 20 Jun 2023 10:54:33 +0200 Subject: [PATCH 075/100] chore(dependabot): ignore patch updates --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 105324d..033a279 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,7 @@ updates: commit-message: prefix: "NuGet" include: "scope" + ignore: + # ignore patch updates + - dependency-name: "*" + update-types: ["version-update:semver-patch"] From c62e2684fc0b6262f0e4b3ae0cead096b0eafc3e Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 20 Jun 2023 10:54:33 +0200 Subject: [PATCH 076/100] chore(dependabot): ignore patch updates --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 105324d..033a279 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,7 @@ updates: commit-message: prefix: "NuGet" include: "scope" + ignore: + # ignore patch updates + - dependency-name: "*" + update-types: ["version-update:semver-patch"] From bc8cb5d50150c3ccb320f2268006ba577ada3289 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Thu, 20 Jul 2023 14:29:47 +0200 Subject: [PATCH 077/100] chore: enable .NET analyzers --- src/Commands.cs | 142 +++++++++++++++++++------------------- src/PromptConstructor.cs | 13 +++- src/ScriptLauncher.csproj | 3 +- 3 files changed, 84 insertions(+), 74 deletions(-) diff --git a/src/Commands.cs b/src/Commands.cs index 5337cc6..7d0c5a5 100644 --- a/src/Commands.cs +++ b/src/Commands.cs @@ -6,46 +6,29 @@ using Spectre.Console.Cli; namespace ScriptLauncher; internal sealed class RootCommand : AsyncCommand +{ + private const int Failure = 1; + private const int Success = 0; + + public override async Task ExecuteAsync( + CommandContext context, + RootCommandSettings settings + ) { - private const int Failure = 1; - private const int Success = 0; - - public override async Task ExecuteAsync( - CommandContext context, - RootCommandSettings settings - ) + if (!Directory.Exists(settings.Directory)) { - if (!Directory.Exists(settings.Directory)) - { - AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); - return Failure; - } + AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); + return Failure; + } - FileInfo[] files; - var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); + FileInfo[] files; + var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); - if (settings.Group) - { - var dict = finder.GetScriptsByDirectory(); + if (settings.Group) + { + var dict = finder.GetScriptsByDirectory(); - if (dict.Count == 0) - { - AnsiConsole.Markup( - $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" - ); - return Failure; - } - - var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(dirPrompt); - files = dict[directoryInfo]; - } - else - { - files = finder.GetScripts(); - } - - if (files.Length == 0) + if (dict.Count == 0) { AnsiConsole.Markup( $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" @@ -53,46 +36,65 @@ internal sealed class RootCommand : AsyncCommand return Failure; } - var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); - var scripts = AnsiConsole.Prompt(prompt); - - try - { - await ScriptExecutor.ExecAsync(scripts, settings.Elevated); - } - catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) - { - AnsiConsole.Markup($"[red]{ex.Message}[/]"); - return Failure; - } - - return Success; + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); + files = dict[directoryInfo]; } + else + { + files = finder.GetScripts(); + } + + if (files.Length == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); + var scripts = AnsiConsole.Prompt(prompt); + + try + { + await ScriptExecutor.ExecAsync(scripts, settings.Elevated).ConfigureAwait(false); + } + catch (Exception ex) + when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException + ) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + return Failure; + } + + return Success; } +} - internal class RootCommandSettings : CommandSettings - { - [Description("Comma separated list of script extensions")] - [CommandOption("-x|--extensions")] - public string? Extensions { get; init; } +internal class RootCommandSettings : CommandSettings +{ + [Description("Comma separated list of script extensions")] + [CommandOption("-x|--extensions")] + public string? Extensions { get; init; } - [Description("Search depth")] - [CommandOption("-d|--depth")] - public int Depth { get; init; } = 1; + [Description("Search depth")] + [CommandOption("-d|--depth")] + public int Depth { get; init; } = 1; - [Description("Run with elevated privileges")] - [CommandOption("-e|--elevated")] - public bool Elevated { get; init; } = false; + [Description("Run with elevated privileges")] + [CommandOption("-e|--elevated")] + public bool Elevated { get; init; } = false; - [Description("Group scripts by folder")] - [CommandOption("-g|--group")] - public bool Group { get; init; } = false; + [Description("Group scripts by folder")] + [CommandOption("-g|--group")] + public bool Group { get; init; } = false; - [Description("Show brief information")] - [CommandOption("-b|--brief")] - public bool Brief { get; init; } = false; + [Description("Show brief information")] + [CommandOption("-b|--brief")] + public bool Brief { get; init; } = false; - [Description("Starting directory (Default: .)")] - [CommandArgument(0, "")] - public string Directory { get; init; } = "."; - } + [Description("Starting directory (Default: .)")] + [CommandArgument(0, "")] + public string Directory { get; init; } = "."; +} diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index 8c0952e..baa6137 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Text; using Spectre.Console; @@ -16,12 +17,18 @@ internal static class PromptConstructor if (!brief) { - builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); + builder.Append( + CultureInfo.InvariantCulture, + $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]" + ); } builder - .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") - .Append($"[greenyellow]{info.Extension}[/]"); + .Append( + CultureInfo.InvariantCulture, + $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]" + ) + .Append(CultureInfo.InvariantCulture, $"[greenyellow]{info.Extension}[/]"); return builder.ToString(); } diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index c293f25..1b1bd1d 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,7 +5,8 @@ net6.0 enable enable - 0.1.3 + All + 0.1.4 true Tool to find and exec shell scripts README.md From 17e99dd0c221b637c09811b7bf66a92c540d9d84 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Thu, 20 Jul 2023 14:29:47 +0200 Subject: [PATCH 078/100] chore: enable .NET analyzers --- src/Commands.cs | 142 +++++++++++++++++++------------------- src/PromptConstructor.cs | 13 +++- src/ScriptLauncher.csproj | 3 +- 3 files changed, 84 insertions(+), 74 deletions(-) diff --git a/src/Commands.cs b/src/Commands.cs index 5337cc6..7d0c5a5 100644 --- a/src/Commands.cs +++ b/src/Commands.cs @@ -6,46 +6,29 @@ using Spectre.Console.Cli; namespace ScriptLauncher; internal sealed class RootCommand : AsyncCommand +{ + private const int Failure = 1; + private const int Success = 0; + + public override async Task ExecuteAsync( + CommandContext context, + RootCommandSettings settings + ) { - private const int Failure = 1; - private const int Success = 0; - - public override async Task ExecuteAsync( - CommandContext context, - RootCommandSettings settings - ) + if (!Directory.Exists(settings.Directory)) { - if (!Directory.Exists(settings.Directory)) - { - AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); - return Failure; - } + AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); + return Failure; + } - FileInfo[] files; - var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); + FileInfo[] files; + var finder = new ScriptFinder(settings.Extensions, settings.Directory, settings.Depth); - if (settings.Group) - { - var dict = finder.GetScriptsByDirectory(); + if (settings.Group) + { + var dict = finder.GetScriptsByDirectory(); - if (dict.Count == 0) - { - AnsiConsole.Markup( - $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" - ); - return Failure; - } - - var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); - var directoryInfo = AnsiConsole.Prompt(dirPrompt); - files = dict[directoryInfo]; - } - else - { - files = finder.GetScripts(); - } - - if (files.Length == 0) + if (dict.Count == 0) { AnsiConsole.Markup( $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" @@ -53,46 +36,65 @@ internal sealed class RootCommand : AsyncCommand return Failure; } - var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); - var scripts = AnsiConsole.Prompt(prompt); - - try - { - await ScriptExecutor.ExecAsync(scripts, settings.Elevated); - } - catch (Exception ex) when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) - { - AnsiConsole.Markup($"[red]{ex.Message}[/]"); - return Failure; - } - - return Success; + var dirPrompt = PromptConstructor.GetDirectoryPrompt(dict.Keys.ToArray()); + var directoryInfo = AnsiConsole.Prompt(dirPrompt); + files = dict[directoryInfo]; } + else + { + files = finder.GetScripts(); + } + + if (files.Length == 0) + { + AnsiConsole.Markup( + $"[red]No scripts script files found in '{finder.RootDirectory}' with extensions '{string.Join(", ", finder.Extensions)}'[/]" + ); + return Failure; + } + + var prompt = PromptConstructor.GetScriptPrompt(files, settings.Brief); + var scripts = AnsiConsole.Prompt(prompt); + + try + { + await ScriptExecutor.ExecAsync(scripts, settings.Elevated).ConfigureAwait(false); + } + catch (Exception ex) + when (ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException + ) + { + AnsiConsole.Markup($"[red]{ex.Message}[/]"); + return Failure; + } + + return Success; } +} - internal class RootCommandSettings : CommandSettings - { - [Description("Comma separated list of script extensions")] - [CommandOption("-x|--extensions")] - public string? Extensions { get; init; } +internal class RootCommandSettings : CommandSettings +{ + [Description("Comma separated list of script extensions")] + [CommandOption("-x|--extensions")] + public string? Extensions { get; init; } - [Description("Search depth")] - [CommandOption("-d|--depth")] - public int Depth { get; init; } = 1; + [Description("Search depth")] + [CommandOption("-d|--depth")] + public int Depth { get; init; } = 1; - [Description("Run with elevated privileges")] - [CommandOption("-e|--elevated")] - public bool Elevated { get; init; } = false; + [Description("Run with elevated privileges")] + [CommandOption("-e|--elevated")] + public bool Elevated { get; init; } = false; - [Description("Group scripts by folder")] - [CommandOption("-g|--group")] - public bool Group { get; init; } = false; + [Description("Group scripts by folder")] + [CommandOption("-g|--group")] + public bool Group { get; init; } = false; - [Description("Show brief information")] - [CommandOption("-b|--brief")] - public bool Brief { get; init; } = false; + [Description("Show brief information")] + [CommandOption("-b|--brief")] + public bool Brief { get; init; } = false; - [Description("Starting directory (Default: .)")] - [CommandArgument(0, "")] - public string Directory { get; init; } = "."; - } + [Description("Starting directory (Default: .)")] + [CommandArgument(0, "")] + public string Directory { get; init; } = "."; +} diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index 8c0952e..baa6137 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Text; using Spectre.Console; @@ -16,12 +17,18 @@ internal static class PromptConstructor if (!brief) { - builder.Append($"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]"); + builder.Append( + CultureInfo.InvariantCulture, + $"[blue]{info.DirectoryName}{Path.DirectorySeparatorChar}[/]" + ); } builder - .Append($"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]") - .Append($"[greenyellow]{info.Extension}[/]"); + .Append( + CultureInfo.InvariantCulture, + $"[orangered1]{Path.GetFileNameWithoutExtension(info.Name)}[/]" + ) + .Append(CultureInfo.InvariantCulture, $"[greenyellow]{info.Extension}[/]"); return builder.ToString(); } diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index c293f25..1b1bd1d 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -5,7 +5,8 @@ net6.0 enable enable - 0.1.3 + All + 0.1.4 true Tool to find and exec shell scripts README.md From 9d9b06dc9359d7b963362852f736b4f87ac12256 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 15 Sep 2023 21:11:41 +0200 Subject: [PATCH 079/100] Update codeql.yml --- .github/workflows/codeql.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 67cdc2a..3b0b0c9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,8 +5,6 @@ on: branches: [ "main", "develop" ] pull_request: branches: [ "main" ] - schedule: - - cron: '00 18 * * 5' jobs: analyze: From 1c04a89bbd86b9f38e5030447c6603ab15acdb19 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 15 Sep 2023 21:11:41 +0200 Subject: [PATCH 080/100] Update codeql.yml --- .github/workflows/codeql.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 67cdc2a..3b0b0c9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,8 +5,6 @@ on: branches: [ "main", "develop" ] pull_request: branches: [ "main" ] - schedule: - - cron: '00 18 * * 5' jobs: analyze: From 8011e27cf69d68925b69df3d9ef0cadebb17a780 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 18 Oct 2023 12:13:15 +0200 Subject: [PATCH 081/100] make dependabot update github-actions --- .github/dependabot.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 033a279..e46e38c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,3 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - version: 2 updates: - package-ecosystem: "nuget" @@ -13,6 +8,15 @@ updates: prefix: "NuGet" include: "scope" ignore: - # ignore patch updates - dependency-name: "*" update-types: ["version-update:semver-patch"] + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "Github Actions" + include: "scope" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor"] From 93f4c85baac75f278f926be9650ebf2fd56cc85d Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Wed, 18 Oct 2023 12:13:15 +0200 Subject: [PATCH 082/100] make dependabot update github-actions --- .github/dependabot.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 033a279..e46e38c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,3 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - version: 2 updates: - package-ecosystem: "nuget" @@ -13,6 +8,15 @@ updates: prefix: "NuGet" include: "scope" ignore: - # ignore patch updates - dependency-name: "*" update-types: ["version-update:semver-patch"] + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "Github Actions" + include: "scope" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor"] From e40506bd035dd7dec4d69a79375efdeb5714ddfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:10:43 +0200 Subject: [PATCH 083/100] Github Actions(deps): Bump actions/checkout from 3 to 4 (#8) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3b0b0c9..f5d7731 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 From 10075c1f37d473aa206e91b265f5b5e8db997426 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:10:43 +0200 Subject: [PATCH 084/100] Github Actions(deps): Bump actions/checkout from 3 to 4 (#8) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3b0b0c9..f5d7731 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 From 61809ded2783ccd692627df403268b9f3ab164f9 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 14 Nov 2023 18:58:17 +0100 Subject: [PATCH 085/100] add `.sln` file --- ScriptLauncher.sln | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 ScriptLauncher.sln diff --git a/ScriptLauncher.sln b/ScriptLauncher.sln new file mode 100644 index 0000000..50bc852 --- /dev/null +++ b/ScriptLauncher.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptLauncher", "src\ScriptLauncher.csproj", "{BCE34935-5D4F-4B81-90FD-9FA22CCCF642}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From aebe1f7aaaa9ff3cb83fae5e25444ab06ce2ae7c Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 14 Nov 2023 19:10:31 +0100 Subject: [PATCH 086/100] bump dotnet version to `net8.0` - bump dotnet verison to .NET 8 - bump tool version to 0.1.5 - fix warnings --- src/Commands.cs | 8 +++-- src/PromptConstructor.cs | 3 +- src/ScriptExecutor.cs | 75 +++++++++++++++++++++++---------------- src/ScriptFinder.cs | 7 ++-- src/ScriptLauncher.csproj | 6 ++-- 5 files changed, 60 insertions(+), 39 deletions(-) diff --git a/src/Commands.cs b/src/Commands.cs index 7d0c5a5..4be0a59 100644 --- a/src/Commands.cs +++ b/src/Commands.cs @@ -5,7 +5,7 @@ using Spectre.Console.Cli; namespace ScriptLauncher; -internal sealed class RootCommand : AsyncCommand +public sealed class RootCommand : AsyncCommand { private const int Failure = 1; private const int Success = 0; @@ -15,6 +15,8 @@ internal sealed class RootCommand : AsyncCommand RootCommandSettings settings ) { + ArgumentNullException.ThrowIfNull(settings); + if (!Directory.Exists(settings.Directory)) { AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); @@ -72,7 +74,7 @@ internal sealed class RootCommand : AsyncCommand } } -internal class RootCommandSettings : CommandSettings +public sealed class RootCommandSettings : CommandSettings { [Description("Comma separated list of script extensions")] [CommandOption("-x|--extensions")] @@ -97,4 +99,4 @@ internal class RootCommandSettings : CommandSettings [Description("Starting directory (Default: .)")] [CommandArgument(0, "")] public string Directory { get; init; } = "."; -} +} \ No newline at end of file diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index baa6137..c624387 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -1,6 +1,7 @@ +using Spectre.Console; + using System.Globalization; using System.Text; -using Spectre.Console; namespace ScriptLauncher; diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index 2038f5c..77e1fe0 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -4,43 +4,58 @@ namespace ScriptLauncher; internal static class ScriptExecutor { - public static async Task ExecAsync(List files, bool elevated) => - await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); + public static async Task ExecAsync(List files, bool elevated) => + await Parallel + .ForEachAsync(files, (file, ct) => ExecAsync(file, elevated, ct)) + .ConfigureAwait(ConfigureAwaitOptions.None); - private static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + private static async ValueTask ExecAsync( + FileInfo file, + bool elevated, + CancellationToken cancellationToken = default + ) { var process = GetExecutableProcessInfo(file, elevated); if (process is null) { return; } - - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + + await ( + Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask + ).ConfigureAwait(ConfigureAwaitOptions.None); } - private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) => file.Extension switch - { - ".bat" or ".cmd" => new ProcessStartInfo + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) => + file.Extension switch { - FileName = "cmd", - Arguments = $"/Q /C .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".ps1" => new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-NoProfile -ExecutionPolicy Bypass -File .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".sh" or ".zsh" or ".fish" => new ProcessStartInfo - { - FileName = "sh", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - var _ => null - }; -} \ No newline at end of file + ".bat" + or ".cmd" + => new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" + => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" + or ".zsh" + or ".fish" + => new ProcessStartInfo + { + FileName = "sh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + _ => null + }; +} diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs index 375ca4f..3ea2f4a 100644 --- a/src/ScriptFinder.cs +++ b/src/ScriptFinder.cs @@ -2,17 +2,20 @@ namespace ScriptLauncher; internal readonly struct ScriptFinder { - static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + private static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + private static readonly char[] DefaultSeparators = new[] { ',', ' ' }; + public string[] Extensions { get; } public string RootDirectory { get; } public int Depth { get; } + private readonly EnumerationOptions _options; public ScriptFinder(string? extensions, string directory, int depth) { Extensions = extensions - ?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + ?.Split(DefaultSeparators, StringSplitOptions.RemoveEmptyEntries) .ToHashSet() .Select(x => $".{x.TrimStart('.')}") .ToArray() ?? DefaultExtensions; diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 1b1bd1d..7784893 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -1,12 +1,12 @@ - + Exe - net6.0 + net8.0 enable enable All - 0.1.4 + 0.1.5 true Tool to find and exec shell scripts README.md From 8188b2edd7e24d53da6da6eb2df9467cc63beca2 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 14 Nov 2023 19:28:12 +0100 Subject: [PATCH 087/100] add `Directory.Build.Props` to use simplified build output --- Directory.Build.props | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..93d5ad5 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + $(MSBuildThisFileDirectory)artifacts + + From b369926aa189c1796d5b02d9e75451355e3128e3 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 14 Nov 2023 18:58:17 +0100 Subject: [PATCH 088/100] add `.sln` file --- ScriptLauncher.sln | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 ScriptLauncher.sln diff --git a/ScriptLauncher.sln b/ScriptLauncher.sln new file mode 100644 index 0000000..50bc852 --- /dev/null +++ b/ScriptLauncher.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptLauncher", "src\ScriptLauncher.csproj", "{BCE34935-5D4F-4B81-90FD-9FA22CCCF642}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCE34935-5D4F-4B81-90FD-9FA22CCCF642}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From f41a11c03a403fa76b2d244b1d1cbc606de34f9b Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Tue, 14 Nov 2023 19:10:31 +0100 Subject: [PATCH 089/100] bump dotnet version to `net8.0` - bump dotnet verison to .NET 8 - bump tool version to 0.1.5 - fix warnings --- src/Commands.cs | 8 +++-- src/PromptConstructor.cs | 3 +- src/ScriptExecutor.cs | 75 +++++++++++++++++++++++---------------- src/ScriptFinder.cs | 7 ++-- src/ScriptLauncher.csproj | 6 ++-- 5 files changed, 60 insertions(+), 39 deletions(-) diff --git a/src/Commands.cs b/src/Commands.cs index 7d0c5a5..4be0a59 100644 --- a/src/Commands.cs +++ b/src/Commands.cs @@ -5,7 +5,7 @@ using Spectre.Console.Cli; namespace ScriptLauncher; -internal sealed class RootCommand : AsyncCommand +public sealed class RootCommand : AsyncCommand { private const int Failure = 1; private const int Success = 0; @@ -15,6 +15,8 @@ internal sealed class RootCommand : AsyncCommand RootCommandSettings settings ) { + ArgumentNullException.ThrowIfNull(settings); + if (!Directory.Exists(settings.Directory)) { AnsiConsole.Markup($"[red]The directory '{settings.Directory}' does not exist.[/]"); @@ -72,7 +74,7 @@ internal sealed class RootCommand : AsyncCommand } } -internal class RootCommandSettings : CommandSettings +public sealed class RootCommandSettings : CommandSettings { [Description("Comma separated list of script extensions")] [CommandOption("-x|--extensions")] @@ -97,4 +99,4 @@ internal class RootCommandSettings : CommandSettings [Description("Starting directory (Default: .)")] [CommandArgument(0, "")] public string Directory { get; init; } = "."; -} +} \ No newline at end of file diff --git a/src/PromptConstructor.cs b/src/PromptConstructor.cs index baa6137..c624387 100644 --- a/src/PromptConstructor.cs +++ b/src/PromptConstructor.cs @@ -1,6 +1,7 @@ +using Spectre.Console; + using System.Globalization; using System.Text; -using Spectre.Console; namespace ScriptLauncher; diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index 2038f5c..77e1fe0 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -4,43 +4,58 @@ namespace ScriptLauncher; internal static class ScriptExecutor { - public static async Task ExecAsync(List files, bool elevated) => - await Parallel.ForEachAsync(files, (x, ct) => ExecAsync(x, elevated, ct)); + public static async Task ExecAsync(List files, bool elevated) => + await Parallel + .ForEachAsync(files, (file, ct) => ExecAsync(file, elevated, ct)) + .ConfigureAwait(ConfigureAwaitOptions.None); - private static async ValueTask ExecAsync(FileInfo file, bool elevated, CancellationToken cancellationToken = default) + private static async ValueTask ExecAsync( + FileInfo file, + bool elevated, + CancellationToken cancellationToken = default + ) { var process = GetExecutableProcessInfo(file, elevated); if (process is null) { return; } - - await (Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask); + + await ( + Process.Start(process)?.WaitForExitAsync(cancellationToken) ?? Task.CompletedTask + ).ConfigureAwait(ConfigureAwaitOptions.None); } - private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) => file.Extension switch - { - ".bat" or ".cmd" => new ProcessStartInfo + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) => + file.Extension switch { - FileName = "cmd", - Arguments = $"/Q /C .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".ps1" => new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-NoProfile -ExecutionPolicy Bypass -File .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".sh" or ".zsh" or ".fish" => new ProcessStartInfo - { - FileName = "sh", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - var _ => null - }; -} \ No newline at end of file + ".bat" + or ".cmd" + => new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/Q /C .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".ps1" + => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -File .\\{file.Name}", + Verb = elevated ? "runas /user:Administrator" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + ".sh" + or ".zsh" + or ".fish" + => new ProcessStartInfo + { + FileName = "sh", + Arguments = $"-c ./{file.Name}", + Verb = elevated ? "sudo" : string.Empty, + WorkingDirectory = file.DirectoryName + }, + _ => null + }; +} diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs index 375ca4f..3ea2f4a 100644 --- a/src/ScriptFinder.cs +++ b/src/ScriptFinder.cs @@ -2,17 +2,20 @@ namespace ScriptLauncher; internal readonly struct ScriptFinder { - static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + private static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + private static readonly char[] DefaultSeparators = new[] { ',', ' ' }; + public string[] Extensions { get; } public string RootDirectory { get; } public int Depth { get; } + private readonly EnumerationOptions _options; public ScriptFinder(string? extensions, string directory, int depth) { Extensions = extensions - ?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + ?.Split(DefaultSeparators, StringSplitOptions.RemoveEmptyEntries) .ToHashSet() .Select(x => $".{x.TrimStart('.')}") .ToArray() ?? DefaultExtensions; diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 1b1bd1d..7784893 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -1,12 +1,12 @@ - + Exe - net6.0 + net8.0 enable enable All - 0.1.4 + 0.1.5 true Tool to find and exec shell scripts README.md From 554e0ef3086d29d50f6a44419cadd2f816630ac5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 09:25:52 +0100 Subject: [PATCH 090/100] NuGet(deps): Bump Spectre.Console from 0.47.0 to 0.48.0 in /src (#10) Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.47.0 to 0.48.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.47.0...0.48.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 7784893..8bcae2e 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -1,4 +1,4 @@ - + Exe @@ -14,7 +14,7 @@ - + From 2a9681467840372e072056536725aa819cff00ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 09:31:49 +0100 Subject: [PATCH 091/100] NuGet(deps): Bump Spectre.Console.Cli from 0.47.0 to 0.48.0 in /src (#9) Bumps [Spectre.Console.Cli](https://github.com/spectreconsole/spectre.console) from 0.47.0 to 0.48.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.47.0...0.48.0) --- updated-dependencies: - dependency-name: Spectre.Console.Cli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 8bcae2e..34e95ff 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -15,7 +15,7 @@ - + From 7f257ef28d08dd1e03fba8cbe48a860f9ee53ee6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:27:43 +0100 Subject: [PATCH 092/100] Github Actions(deps): Bump github/codeql-action from 2 to 3 (#11) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f5d7731..0f9354d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,16 +25,16 @@ jobs: uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" From 4dd6a9eb325d596759a20ee55e7cef6a8b1c9f7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:19:26 +0200 Subject: [PATCH 093/100] NuGet(deps): Bump Spectre.Console from 0.48.0 to 0.49.1 in /src (#13) Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.48.0 to 0.49.1. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.48.0...0.49.1) --- updated-dependencies: - dependency-name: Spectre.Console dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 34e95ff..ee565be 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -14,7 +14,7 @@ - + From 7f51cbe41a96e2fa84bdb11f6638e37fa5fd5175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:20:40 +0200 Subject: [PATCH 094/100] NuGet(deps): Bump Spectre.Console.Cli from 0.48.0 to 0.49.1 in /src (#14) Bumps [Spectre.Console.Cli](https://github.com/spectreconsole/spectre.console) from 0.48.0 to 0.49.1. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.48.0...0.49.1) --- updated-dependencies: - dependency-name: Spectre.Console.Cli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index ee565be..9f57695 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -15,7 +15,7 @@ - + From 2497de6e30dc0926282d4c9357494daad2ff42d3 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 25 Oct 2024 00:15:35 +0200 Subject: [PATCH 095/100] use appropriate elevation verb bases on OS --- src/ScriptExecutor.cs | 73 +++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index 77e1fe0..8c1cb4b 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -26,36 +26,49 @@ internal static class ScriptExecutor ).ConfigureAwait(ConfigureAwaitOptions.None); } - private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) => - file.Extension switch + private static string GetElevationVerb(bool elevated) + { + if (!elevated) { - ".bat" - or ".cmd" - => new ProcessStartInfo - { - FileName = "cmd", - Arguments = $"/Q /C .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".ps1" - => new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-NoProfile -ExecutionPolicy Bypass -File .\\{file.Name}", - Verb = elevated ? "runas /user:Administrator" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - ".sh" - or ".zsh" - or ".fish" - => new ProcessStartInfo - { - FileName = "sh", - Arguments = $"-c ./{file.Name}", - Verb = elevated ? "sudo" : string.Empty, - WorkingDirectory = file.DirectoryName - }, - _ => null + return string.Empty; + } + + var platform = Environment.OSVersion.Platform; + return platform switch + { + PlatformID.Win32NT => "runas /user:Administrator", + PlatformID.Unix or PlatformID.MacOSX => "sudo", + _ => string.Empty, }; + } + + private static ProcessStartInfo? GetExecutableProcessInfo(FileInfo file, bool elevated) + { + var verb = GetElevationVerb(elevated); + return file.Extension switch + { + ".bat" or ".cmd" => new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/Q /C {file.Name}", + Verb = verb, + WorkingDirectory = file.DirectoryName, + }, + ".ps1" => new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -File {file.Name}", + Verb = verb, + WorkingDirectory = file.DirectoryName, + }, + ".sh" or ".zsh" or ".fish" => new ProcessStartInfo + { + FileName = "sh", + Arguments = $"-c {file.Name}", + Verb = verb, + WorkingDirectory = file.DirectoryName, + }, + _ => null, + }; + } } From d931d2517dc7a42ebacb79c5dc186449dc8c2b80 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 25 Oct 2024 00:16:00 +0200 Subject: [PATCH 096/100] handle nushell scripts out of the box --- src/ScriptExecutor.cs | 7 +++++++ src/ScriptFinder.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index 8c1cb4b..939e2b0 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -61,6 +61,13 @@ internal static class ScriptExecutor Verb = verb, WorkingDirectory = file.DirectoryName, }, + ".nu" => new ProcessStartInfo + { + FileName = "nu", + Arguments = $"--no-config-file {file.Name}", + Verb = verb, + WorkingDirectory = file.DirectoryName, + }, ".sh" or ".zsh" or ".fish" => new ProcessStartInfo { FileName = "sh", diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs index 3ea2f4a..14554bf 100644 --- a/src/ScriptFinder.cs +++ b/src/ScriptFinder.cs @@ -2,7 +2,7 @@ namespace ScriptLauncher; internal readonly struct ScriptFinder { - private static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd" }; + private static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd", ".nu" }; private static readonly char[] DefaultSeparators = new[] { ',', ' ' }; public string[] Extensions { get; } From ead6488f7c96c2dba6d59866fcb36eebe9f54cb2 Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 25 Oct 2024 00:30:22 +0200 Subject: [PATCH 097/100] fallback on `sh -c` for unknown scripts extensions --- src/ScriptExecutor.cs | 11 +++++------ src/ScriptLauncher.csproj | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ScriptExecutor.cs b/src/ScriptExecutor.cs index 939e2b0..a1d14aa 100644 --- a/src/ScriptExecutor.cs +++ b/src/ScriptExecutor.cs @@ -50,32 +50,31 @@ internal static class ScriptExecutor ".bat" or ".cmd" => new ProcessStartInfo { FileName = "cmd", - Arguments = $"/Q /C {file.Name}", + Arguments = $"/Q /C ./{file.Name}", Verb = verb, WorkingDirectory = file.DirectoryName, }, ".ps1" => new ProcessStartInfo { FileName = "powershell.exe", - Arguments = $"-NoProfile -ExecutionPolicy Bypass -File {file.Name}", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -File ./{file.Name}", Verb = verb, WorkingDirectory = file.DirectoryName, }, ".nu" => new ProcessStartInfo { FileName = "nu", - Arguments = $"--no-config-file {file.Name}", + Arguments = $"--no-config-file ./{file.Name}", Verb = verb, WorkingDirectory = file.DirectoryName, }, - ".sh" or ".zsh" or ".fish" => new ProcessStartInfo + _ => new ProcessStartInfo { FileName = "sh", - Arguments = $"-c {file.Name}", + Arguments = $"-c ./{file.Name}", Verb = verb, WorkingDirectory = file.DirectoryName, }, - _ => null, }; } } diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 9f57695..3bb83d8 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -2,11 +2,11 @@ Exe - net8.0 + net8.0 enable enable All - 0.1.5 + 0.1.6 true Tool to find and exec shell scripts README.md From f067394a845b5a31ea5c406201a0542e7adb2cfb Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Fri, 25 Oct 2024 11:04:07 +0200 Subject: [PATCH 098/100] fix missing default values --- README.md | 17 +++++++++-------- src/Commands.cs | 25 ++++++++++++++----------- src/Program.cs | 6 +++++- src/ScriptFinder.cs | 16 ++++------------ src/ScriptLauncher.csproj | 2 +- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ab7118b..246bb32 100644 --- a/README.md +++ b/README.md @@ -33,18 +33,19 @@ _**NOTE**_: The option `-p:PublishTrimmed=true` may produce some *warnings*. If ```sh USAGE: - scrl [OPTIONS] + scrl [path] [OPTIONS] ARGUMENTS: - Starting directory (Default: .) + [path] Starting directory (Default: .) OPTIONS: - -h, --help Prints help information - -x, --extensions Comma separated list of script extensions - -d, --depth Search depth - -e, --elevated Run with elevated privileges - -g, --group Group scripts by folder - -b, --brief Show brief information + DEFAULT + -h, --help Prints help information + -x, --extensions List of script extensions to search for + -d, --depth 3 Search depth + -e, --elevated Run with elevated privileges + -g, --group Group scripts by folder + -b, --brief Show brief information ``` [CLI]: https://docs.microsoft.com/en-us/dotnet/core/tools/ ".NET CLI Docs" diff --git a/src/Commands.cs b/src/Commands.cs index 4be0a59..ed0b950 100644 --- a/src/Commands.cs +++ b/src/Commands.cs @@ -1,5 +1,5 @@ using System.ComponentModel; - +using System.Diagnostics.CodeAnalysis; using Spectre.Console; using Spectre.Console.Cli; @@ -76,27 +76,30 @@ public sealed class RootCommand : AsyncCommand public sealed class RootCommandSettings : CommandSettings { - [Description("Comma separated list of script extensions")] - [CommandOption("-x|--extensions")] - public string? Extensions { get; init; } + [Description("List of script extensions to search for")] + [CommandOption("-x|--extensions ")] + [SuppressMessage("Performance", "CA1819:Properties should not return arrays")] + public string[] Extensions { get; init; } = [".ps1", ".*sh", ".bat", ".cmd", ".nu"]; [Description("Search depth")] [CommandOption("-d|--depth")] - public int Depth { get; init; } = 1; + [DefaultValue(3)] + public int Depth { get; init; } [Description("Run with elevated privileges")] [CommandOption("-e|--elevated")] - public bool Elevated { get; init; } = false; + public bool Elevated { get; init; } [Description("Group scripts by folder")] [CommandOption("-g|--group")] - public bool Group { get; init; } = false; + public bool Group { get; init; } [Description("Show brief information")] [CommandOption("-b|--brief")] - public bool Brief { get; init; } = false; + public bool Brief { get; init; } [Description("Starting directory (Default: .)")] - [CommandArgument(0, "")] - public string Directory { get; init; } = "."; -} \ No newline at end of file + [CommandArgument(0, "[path]")] + [DefaultValue(".")] + public string Directory { get; init; } = string.Empty; +} diff --git a/src/Program.cs b/src/Program.cs index f53f375..e57668c 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -2,5 +2,9 @@ using ScriptLauncher; using Spectre.Console.Cli; var app = new CommandApp(); -app.Configure(x => x.SetApplicationName("scrl")); +app.Configure(x => +{ + x.SetApplicationName("scrl"); +}); + return app.Run(args); diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs index 14554bf..618701d 100644 --- a/src/ScriptFinder.cs +++ b/src/ScriptFinder.cs @@ -2,23 +2,15 @@ namespace ScriptLauncher; internal readonly struct ScriptFinder { - private static readonly string[] DefaultExtensions = new[] { ".ps1", ".*sh", ".bat", ".cmd", ".nu" }; - private static readonly char[] DefaultSeparators = new[] { ',', ' ' }; - - public string[] Extensions { get; } + public IEnumerable Extensions { get; } public string RootDirectory { get; } - public int Depth { get; } + private int Depth { get; } private readonly EnumerationOptions _options; - public ScriptFinder(string? extensions, string directory, int depth) + public ScriptFinder(IEnumerable extensions, string directory, int depth) { - Extensions = - extensions - ?.Split(DefaultSeparators, StringSplitOptions.RemoveEmptyEntries) - .ToHashSet() - .Select(x => $".{x.TrimStart('.')}") - .ToArray() ?? DefaultExtensions; + Extensions = extensions.ToHashSet().Select(x => $".{x.TrimStart('.')}"); Depth = depth; RootDirectory = directory; diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 3bb83d8..8cc0034 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -6,7 +6,7 @@ enable enable All - 0.1.6 + 0.1.7 true Tool to find and exec shell scripts README.md From 5e16b6f5b0613eb9c1bb18df8d43b90a944ee64c Mon Sep 17 00:00:00 2001 From: Marcello Lamonaca Date: Sun, 10 Nov 2024 10:53:23 +0100 Subject: [PATCH 099/100] make lambdas static where possible --- src/Program.cs | 5 +---- src/ScriptFinder.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index e57668c..836c56b 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -2,9 +2,6 @@ using ScriptLauncher; using Spectre.Console.Cli; var app = new CommandApp(); -app.Configure(x => -{ - x.SetApplicationName("scrl"); -}); +app.Configure(static x => x.SetApplicationName("scrl")); return app.Run(args); diff --git a/src/ScriptFinder.cs b/src/ScriptFinder.cs index 618701d..e7040f4 100644 --- a/src/ScriptFinder.cs +++ b/src/ScriptFinder.cs @@ -10,7 +10,7 @@ internal readonly struct ScriptFinder public ScriptFinder(IEnumerable extensions, string directory, int depth) { - Extensions = extensions.ToHashSet().Select(x => $".{x.TrimStart('.')}"); + Extensions = extensions.ToHashSet().Select(static x => $".{x.TrimStart('.')}"); Depth = depth; RootDirectory = directory; @@ -28,7 +28,7 @@ internal readonly struct ScriptFinder try { var filenames = Directory.GetFiles(RootDirectory, $"*{extension}", _options); - return filenames.Select(x => new FileInfo(x)); + return filenames.Select(static x => new FileInfo(x)); } catch (UnauthorizedAccessException) { @@ -37,13 +37,13 @@ internal readonly struct ScriptFinder } public FileInfo[] GetScripts() => - Extensions.Select(GetScriptFilesWithExtension).SelectMany(x => x).ToArray(); + Extensions.Select(GetScriptFilesWithExtension).SelectMany(static x => x).ToArray(); public IDictionary GetScriptsByDirectory() => Extensions .Select(GetScriptFilesWithExtension) - .SelectMany(x => x) - .GroupBy(x => x.DirectoryName!) - .OrderBy(x => x.Key) - .ToDictionary(x => new DirectoryInfo(x.Key), x => x.ToArray()); + .SelectMany(static x => x) + .GroupBy(static x => x.DirectoryName!) + .OrderBy(static x => x.Key) + .ToDictionary(static x => new DirectoryInfo(x.Key), static x => x.ToArray()); } From 50b5e41c694b8af1b3c2ad4ed90d4b2b89c3f50c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 09:29:28 +0200 Subject: [PATCH 100/100] NuGet(deps): Bump Spectre.Console from 0.49.1 to 0.50.0 in /src (#15) Bumps [Spectre.Console](https://github.com/spectreconsole/spectre.console) from 0.49.1 to 0.50.0. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.49.1...0.50.0) --- updated-dependencies: - dependency-name: Spectre.Console dependency-version: 0.50.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/ScriptLauncher.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ScriptLauncher.csproj b/src/ScriptLauncher.csproj index 8cc0034..985e1e3 100644 --- a/src/ScriptLauncher.csproj +++ b/src/ScriptLauncher.csproj @@ -14,8 +14,8 @@ - - + +