├── .config
└── dotnet-tools.json
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature_request.md
└── workflows
│ └── build.yml
├── .gitignore
├── .paket
└── Paket.Restore.targets
├── LICENSE
├── README.md
├── RELEASE_NOTES.md
├── build.fsx
├── logo.png
├── paket.dependencies
├── paket.lock
└── src
├── Fli.Tests
├── ExecContext
│ ├── ExecCommandConfigureUnixTests.fs
│ ├── ExecCommandConfigureWindowsTests.fs
│ ├── ExecCommandExecuteUnixTests.fs
│ ├── ExecCommandExecuteWindowsTests.fs
│ ├── ExecCommandToStringTests.fs
│ └── ExecConfigTests.fs
├── Fli.Tests.fsproj
├── ShellContext
│ ├── ShellCommandConfigureUnixTests.fs
│ ├── ShellCommandConfigureWindowsTests.fs
│ ├── ShellCommandExecuteUnixTests.fs
│ ├── ShellCommandExecuteWindowsTests.fs
│ ├── ShellCommandToStringUnixTests.fs
│ ├── ShellCommandToStringWindowsTests.fs
│ └── ShellConfigTests.fs
└── paket.references
├── Fli.sln
├── Fli
├── AssemblyInfo.fs
├── CE.fs
├── Command.fs
├── Domain.fs
├── Dsl.fs
├── Extensions.fs
├── Fli.fsproj
├── Helpers.fs
├── Output.fs
├── paket.references
└── paket.template
└── install.ps1
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "paket": {
6 | "version": "9.0.2",
7 | "commands": [
8 | "paket"
9 | ],
10 | "rollForward": false
11 | },
12 | "fantomas": {
13 | "version": "7.0.1",
14 | "commands": [
15 | "fantomas"
16 | ],
17 | "rollForward": false
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: CaptnCodr
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a bug report
4 | title: ''
5 | labels: bug, triage
6 | assignees: CaptnCodr
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Repro steps**
14 | Please provide the steps required to reproduce the problem
15 | 1. Abstract code you wrote
16 | 2. Executed code
17 | 3. See error
18 |
19 | **Expected behavior**
20 | Please provide a description of the behavior you expect.
21 |
22 | **Errors or actual behavior**
23 | If applicable, add error messages
24 |
25 | **Related information (please complete the following information):**
26 | - OS: [e.g.: Windows, Linux, MacOS]
27 | - .NET version
28 |
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement, feature
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build:
13 |
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | os: [windows-latest, macOS-latest, ubuntu-latest]
18 | dotnet: [8.0.100]
19 | runs-on: ${{ matrix.os }}
20 |
21 | steps:
22 | - uses: actions/checkout@v3
23 | - name: Setup .NET
24 | uses: actions/setup-dotnet@v3
25 | with:
26 | dotnet-version: ${{ matrix.dotnet }}
27 |
28 | - name: Install local tools
29 | run: dotnet tool restore
30 |
31 | - name: Paket Restore
32 | run: dotnet paket restore
33 |
34 | - name: Build
35 | run: dotnet build ./src/
36 |
37 | - name: Test
38 | run: dotnet test ./src/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
352 | # Rider's working folder.
353 | .idea/
--------------------------------------------------------------------------------
/.paket/Paket.Restore.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
8 |
9 | $(MSBuildVersion)
10 | 15.0.0
11 | false
12 | true
13 |
14 | true
15 | $(MSBuildThisFileDirectory)
16 | $(MSBuildThisFileDirectory)..\
17 | $(PaketRootPath)paket-files\paket.restore.cached
18 | $(PaketRootPath)paket.lock
19 | classic
20 | proj
21 | assembly
22 | native
23 | /Library/Frameworks/Mono.framework/Commands/mono
24 | mono
25 |
26 |
27 | $(PaketRootPath)paket.bootstrapper.exe
28 | $(PaketToolsPath)paket.bootstrapper.exe
29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\
30 |
31 | "$(PaketBootStrapperExePath)"
32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"
33 |
34 |
35 |
36 |
37 | true
38 | true
39 |
40 |
41 | True
42 |
43 |
44 | False
45 |
46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/'))
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | $(PaketRootPath)paket
56 | $(PaketToolsPath)paket
57 |
58 |
59 |
60 |
61 |
62 | $(PaketRootPath)paket.exe
63 | $(PaketToolsPath)paket.exe
64 |
65 |
66 |
67 |
68 |
69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json"))
70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"'))
71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | <_PaketCommand>dotnet paket
83 |
84 |
85 |
86 |
87 |
88 | $(PaketToolsPath)paket
89 | $(PaketBootStrapperExeDir)paket
90 |
91 |
92 | paket
93 |
94 |
95 |
96 |
97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))
98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)"
99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)"
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | true
122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608
123 | false
124 | true
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))
134 |
135 |
136 |
137 |
138 |
139 |
141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``))
142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``))
143 |
144 |
145 |
146 |
147 | %(PaketRestoreCachedKeyValue.Value)
148 | %(PaketRestoreCachedKeyValue.Value)
149 |
150 |
151 |
152 |
153 | true
154 | false
155 | true
156 |
157 |
158 |
162 |
163 | true
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached
183 |
184 | $(MSBuildProjectFullPath).paket.references
185 |
186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
187 |
188 | $(MSBuildProjectDirectory)\paket.references
189 |
190 | false
191 | true
192 | true
193 | references-file-or-cache-not-found
194 |
195 |
196 |
197 |
198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)'))
199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)'))
200 | references-file
201 | false
202 |
203 |
204 |
205 |
206 | false
207 |
208 |
209 |
210 |
211 | true
212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | false
224 | true
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)
236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])
237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])
238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[2])
239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])
240 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])
241 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6])
242 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7])
243 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8])
244 |
245 |
246 | %(PaketReferencesFileLinesInfo.PackageVersion)
247 | All
248 | runtime
249 | $(ExcludeAssets);contentFiles
250 | $(ExcludeAssets);build;buildMultitargeting;buildTransitive
251 | %(PaketReferencesFileLinesInfo.Aliases)
252 | true
253 | true
254 |
255 |
256 |
257 |
258 | %(PaketReferencesFileLinesInfo.PackageVersion)
259 |
260 |
261 |
262 |
263 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0])
273 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1])
274 |
275 |
276 | %(PaketCliToolFileLinesInfo.PackageVersion)
277 |
278 |
279 |
280 |
284 |
285 |
286 |
287 |
288 |
289 | false
290 |
291 |
292 |
293 |
294 |
295 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/>
296 |
297 |
298 |
299 |
300 |
301 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile)
302 | true
303 | false
304 | true
305 | false
306 | true
307 | false
308 | true
309 | false
310 | true
311 | false
312 | true
313 | $(PaketIntermediateOutputPath)\$(Configuration)
314 | $(PaketIntermediateOutputPath)
315 |
316 |
317 |
318 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/>
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
378 |
379 |
428 |
429 |
474 |
475 |
519 |
520 |
563 |
564 |
565 |
566 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Constantin Tews
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fli
2 | [](https://github.com/CaptnCodr/Fli/actions/workflows/build.yml)
3 | [](https://www.nuget.org/packages/Fli/)
4 |
5 |
6 | Execute CLI commands from your F# code in F# style!
7 |
8 | **Fli is part of the F# Advent Calendar 2022: [A little story about Fli](https://gist.github.com/CaptnCodr/d709b30eb1191bedda090623d04bf738)**
9 |
10 | ### Features
11 | - Starting processes easily
12 | - Execute CLI commands in your favourite shell
13 | - F# computation expression syntax
14 | - Wrap authenticated CLI tools
15 | - No external dependencies
16 |
17 | ### Install
18 | Get it from [Nuget](https://www.nuget.org/packages/Fli/): `dotnet add package Fli`
19 |
20 | ### Usage
21 | `open Fli` and start
22 |
23 | For example:
24 | ```fsharp
25 | cli {
26 | Shell CMD
27 | Command "echo Hello World!"
28 | }
29 | |> Command.execute
30 | ```
31 | that starts `CMD.exe` as Shell and `echo Hello World!` is the command to execute.
32 |
33 | Run a file with PowerShell from a specific directory:
34 | ```fsharp
35 | cli {
36 | Shell PWSH
37 | Command "test.bat"
38 | WorkingDirectory (Environment.GetFolderPath Environment.SpecialFolder.UserProfile)
39 | }
40 | |> Command.execute
41 | ```
42 |
43 | Executing programs with arguments:
44 | ```fsharp
45 | cli {
46 | Exec "path/to/executable"
47 | Arguments "--info"
48 | }
49 | |> Command.execute
50 | ```
51 |
52 | an example with `git`:
53 | ```fsharp
54 | cli {
55 | Exec "git"
56 | Arguments ["commit"; "-m"; "\"Fixing issue #1337.\""]
57 | }
58 | |> Command.execute
59 | ```
60 |
61 | Add a verb to your executing program:
62 | ```fsharp
63 | cli {
64 | Exec "adobe.exe"
65 | Arguments (Path.Combine ((Environment.GetFolderPath Environment.SpecialFolder.UserProfile), "test.pdf"))
66 | Verb "open"
67 | }
68 | |> Command.execute
69 | ```
70 | or open a file in the default/assigned program:
71 | ```fsharp
72 | cli {
73 | Exec "test.pdf"
74 | }
75 | |> Command.execute
76 | ```
77 | (Hint: if file extension is not assigned to any installed program, it will throw a `System.NullReferenceException`)
78 |
79 | Write output to a specific file:
80 | ```fsharp
81 | cli {
82 | Exec "dotnet"
83 | Arguments "--list-sdks"
84 | Output @"absolute\path\to\dotnet-sdks.txt"
85 | }
86 | |> Command.execute
87 | ```
88 |
89 | Write output to a function (logging, printing, etc.):
90 | ```fsharp
91 | let log (output: string) = Debug.Log($"CLI log: {output}")
92 |
93 | cli {
94 | Exec "dotnet"
95 | Arguments "--list-sdks"
96 | Output log
97 | }
98 | |> Command.execute
99 | ```
100 |
101 | Add environment variables for the executing program:
102 | ```fsharp
103 | cli {
104 | Exec "git"
105 | EnvironmentVariables [("GIT_AUTHOR_NAME", "Jon Doe"); ("GIT_AUTHOR_EMAIL", "jon.doe@domain.com")]
106 | Output ""
107 | }
108 | |> Command.execute
109 | ```
110 | Hint: `Output ""` will be ignored. This is for conditional cases, e.g.: `Output (if true then logFilePath else "")`.
111 |
112 | Add credentials to program:
113 | ```fsharp
114 | cli {
115 | Exec "program"
116 | Credentials ("domain", "bobk", "password123")
117 | }
118 | |> Command.execute
119 | ```
120 | Hint: Running a process as a different user is supported on all platforms. Other options (Domain, Password) are only available on Windows. As an alternative for not Windows based systems there is:
121 | ```fsharp
122 | cli {
123 | Exec "path/to/program"
124 | Username "admin"
125 | }
126 | |> Command.execute
127 | ```
128 |
129 | For Windows applications it's possible to set their visibility. There are four possible values: `Hidden`, `Maximized`, `Minimized` and `Normal`. The default is `Hidden`.
130 | ```fsharp
131 | cli {
132 | Exec @"C:\Windows\regedit.exe"
133 | WindowStyle Normal
134 | }
135 | |> Command.execute
136 | ```
137 |
138 | #### `Command.execute`
139 | `Command.execute` returns record: `type Output = { Id: int; Text: string option; ExitCode: int; Error: string option }`
140 | which has getter methods to get only one value:
141 | ```fsharp
142 | toId: Output -> int
143 | toText: Output -> string
144 | toExitCode: Output -> int
145 | toError: Output -> string
146 | ```
147 | example:
148 | ```fsharp
149 | cli {
150 | Shell CMD
151 | Command "echo Hello World!"
152 | }
153 | |> Command.execute // { Id = 123; Text = Some "Hello World!"; ExitCode = 0; Error = None }
154 | |> Output.toText // "Hello World!"
155 |
156 | // same with Output.toId:
157 | cli { ... }
158 | |> Command.execute // { Id = 123; Text = Some "Hello World!"; ExitCode = 0; Error = None }
159 | |> Output.toId // 123
160 |
161 | // same with Output.toExitCode:
162 | cli { ... }
163 | |> Command.execute // { Id = 123; Text = Some "Hello World!"; ExitCode = 0; Error = None }
164 | |> Output.toExitCode // 0
165 |
166 | // in case of an error:
167 | cli { ... }
168 | |> Command.execute // { Id = 123; Text = None; ExitCode = 1; Error = Some "This is an error!" }
169 | |> Output.toError // "This is an error!"
170 | ```
171 |
172 | #### `Output` functions
173 | ```fsharp
174 | throwIfErrored: Output -> Output
175 | throw: (Output -> bool) -> Output -> Output
176 | ```
177 |
178 | `Output.throw` and `Output.throwIfErrored` are assertion functions that if something's not right it will throw an exception.
179 | That is useful for build scripts to stop the execution immediately, here is an example:
180 | ```fsharp
181 | cli {
182 | Exec "dotnet"
183 | Arguments [| "build"; "-c"; "Release" |]
184 | WorkingDirectory "src/"
185 | }
186 | |> Command.execute // returns { Id = 123; Text = None; ExitCode = 1; Error = Some "This is an error!" }
187 | |> Output.throwIfErrored // <- Exception thrown!
188 | |> Output.toError
189 | ```
190 |
191 | or, you can define when to "fail":
192 | ```fsharp
193 | cli { ... }
194 | |> Command.execute // returns { Id = 123; Text = "An error occured: ..."; ExitCode = 1; Error = Some "Error detail." }
195 | |> Output.throw (fun output -> output.Text.Contains("error")) // <- Exception thrown!
196 | |> Output.toError
197 | ```
198 |
199 | #### Printing `Output` fields
200 | There are printing methods in `Output` too:
201 | ```fsharp
202 | printId: Output -> unit
203 | printText: Output -> unit
204 | printExitCode: Output -> unit
205 | printError: Output -> unit
206 | ```
207 |
208 | Instead of writing:
209 | ```fsharp
210 | cli { ... }
211 | |> Command.execute
212 | |> Output.toText
213 | |> printfn "%s"
214 | ```
215 | For a little shorter code you can use:
216 | ```fsharp
217 | cli { ... }
218 | |> Command.execute
219 | |> Output.printText
220 | ```
221 |
222 | #### `Command.toString`
223 | `Command.toString` concatenates only the executing shell/program + the given commands/arguments:
224 | ```fsharp
225 | cli {
226 | Shell PS
227 | Command "Write-Host Hello World!"
228 | }
229 | |> Command.toString // "powershell.exe -Command Write-Host Hello World!"
230 | ```
231 | and:
232 | ```fsharp
233 | cli {
234 | Exec "cmd.exe"
235 | Arguments [ "/C"; "echo"; "Hello World!" ]
236 | }
237 | |> Command.toString // "cmd.exe /C echo Hello World!"
238 | ```
239 |
240 | #### Builder operations:
241 |
242 | `ShellContext` operations (`cli { Shell ... }`):
243 | | Operation | Type |
244 | |------------------------|----------------------------|
245 | | `Shell` | `Fli.Shells` |
246 | | `Command` | `string` |
247 | | `Input` | `string` |
248 | | `Output` | `Fli.Outputs` |
249 | | `WorkingDirectory` | `string` |
250 | | `WindowStyle` | `Fli.WindowStyle` |
251 | | `EnvironmentVariable` | `string * string` |
252 | | `EnvironmentVariables` | `(string * string) list` |
253 | | `Encoding` | `System.Text.Encoding` |
254 | | `CancelAfter` | `int` |
255 |
256 | `ExecContext` operations (`cli { Exec ... }`):
257 | | Operation | Type |
258 | |------------------------|----------------------------------------------------------|
259 | | `Exec` | `string` |
260 | | `Arguments` | `string` / `string seq` / `string list` / `string array` |
261 | | `Input` | `string` |
262 | | `Output` | `Fli.Outputs` |
263 | | `Verb` | `string` |
264 | | `Username` | `string` |
265 | | `Credentials` | `string * string * string` |
266 | | `WorkingDirectory` | `string` |
267 | | `WindowStyle` | `Fli.WindowStyle` |
268 | | `EnvironmentVariable` | `string * string` |
269 | | `EnvironmentVariables` | `(string * string) list` |
270 | | `Encoding` | `System.Text.Encoding` |
271 | | `CancelAfter` | `int` |
272 |
273 | Currently provided `Fli.Shells`:
274 | - `CMD` runs either `cmd.exe /c ...` or `cmd.exe /k ...` (if `Input` is provided)
275 | - `PS` runs `powershell.exe -Command ...`
276 | - `PWSH` runs `pwsh.exe -Command ...`
277 | - `WSL` runs `wsl.exe -- ...`
278 | - `SH` runs `sh -c ...`
279 | - `BASH` runs `bash -c ...`
280 | - `ZSH` runs `zsh -c ...`
281 | - `CUSTOM (shell: string * flag: string)` runs the specified `shell` with the specified starting argument (`flag`)
282 |
283 | Provided `Fli.Outputs`:
284 | - `File of string` a string with an absolute path of the output file.
285 | - `StringBuilder of StringBuilder` a StringBuilder which will be filled with the output text.
286 | - `Custom of Func` a custom function (`string -> unit`) that will be called with the output string (logging, printing etc.).
287 |
288 | Provided `Fli.WindowStyle`:
289 | - `Hidden` (default)
290 | - `Maximized`
291 | - `Minimized`
292 | - `Normal`
293 |
294 | ### Do you miss something?
295 | Open an [issue](https://github.com/CaptnCodr/Fli/issues) or start a [discussion](https://github.com/CaptnCodr/Fli/discussions).
296 |
297 | ### Contributing
298 | After cloning this repository, there are some steps to start:
299 | 1. `dotnet tool restore`
300 | 2. `dotnet paket restore`
301 | 3. `dotnet restore`
302 | 4. `dotnet paket install`
303 | 5. `dotnet build`
304 |
305 | After that, you can start coding, build and test.
306 |
307 | Every contribution is welcome. :)
308 |
309 | ### Inspiration
310 | Use CE's for CLI commands came in mind while using [FsHttp](https://github.com/fsprojects/FsHttp).
311 |
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | v1.111.11.0 - May 27, 2025
2 | - Improve ArgumentList in `Exec.Arguments`, this will split arguments more precise especially for python commands. (https://github.com/CaptnCodr/Fli/pull/76)
3 | - Improve documentations.
4 |
5 | v1.111.10.0 - May 31, 2024
6 | - Empty file output will not write to file. (https://github.com/CaptnCodr/Fli/pull/72)
7 |
8 | v1.111.1.0 - May 24, 2024
9 | - Ensure StandardIn is always redirected when there is `Input`. (https://github.com/CaptnCodr/Fli/pull/71)
10 |
11 | v1.111.0.0 - Apr 16, 2024
12 | - Add SH and ZSH
13 | - Add Linux's PWSH, always quote default shell (https://github.com/CaptnCodr/Fli/pull/68)
14 |
15 | v1.110.0.0 - Apr 12, 2024
16 | - Add WindowStyle to CE. (https://github.com/CaptnCodr/Fli/pull/67)
17 |
18 | v1.101.0.0 - Jan 09, 2024
19 | - New year's work.
20 |
21 | v1.100.10.0 - Nov 24, 2023
22 | - Tiny logo fix.
23 |
24 | v1.100.1.0 - Nov 24, 2023
25 | - Tiny fix.
26 |
27 | v1.100.0.0 - Nov 24, 2023
28 | - Add .NET8 support (https://github.com/CaptnCodr/Fli/pull/60).
29 | - Add new logo (https://github.com/CaptnCodr/Fli/pull/61).
30 |
31 | v1.11.0.0 - Oct 06, 2023
32 | - Add `throw` and `throwIfErrored` functions to interrupt pipeline on error (https://github.com/CaptnCodr/Fli/pull/56).
33 |
34 | v1.10.1.0 - Sep 01, 2023
35 | - `Command.executeAsync` stdout and stderr content now gets returned properly (https://github.com/CaptnCodr/Fli/pull/58).
36 |
37 | v1.10.0.0 - Aug 11, 2023
38 | - Open files in default/assigned program in `Exec` (https://github.com/CaptnCodr/Fli/pull/56).
39 |
40 | v1.1.1.0 - Jul 25, 2023
41 | - No need to quote the bash command anymore (https://github.com/CaptnCodr/Fli/pull/53) to have a consistent using between `Shells`.
42 |
43 | v1.1.0.0 - May 29, 2023
44 | - Slightly change signature of `Output` CustomOperation. (https://github.com/CaptnCodr/Fli/pull/51)
45 | - Add `CancelAfter` CustomOperation for `executeAsync` to cancel after a specific amount of time (milliseconds). (https://github.com/CaptnCodr/Fli/pull/50)
46 |
47 | v1.0.1.0 - Feb 02, 2023
48 | - Fix FSharp.Core to v6.0. (https://github.com/CaptnCodr/Fli/issues/47)
49 |
50 | v1.0.0.0 - Dec 18, 2022
51 | - Add `CUSTOM` shell in `ShellContext`. (https://github.com/CaptnCodr/Fli/pull/46)
52 | - Add printing methods for each field in `Output` for shorter code. (https://github.com/CaptnCodr/Fli/pull/45)
53 | - Update dependencies.
54 |
55 | v0.11.0.0 - Nov 11, 2022
56 | - Add `Output` as CustomOperation with different types (s. `Fli.Outputs`). (https://github.com/CaptnCodr/Fli/pull/37 https://github.com/CaptnCodr/Fli/pull/39 https://github.com/CaptnCodr/Fli/pull/40 https://github.com/CaptnCodr/Fli/pull/41)
57 | - Add .NET7 support and drop .MET5. (https://github.com/CaptnCodr/Fli/pull/42)
58 | - Update dependencies.
59 |
60 | v0.9.0.0 - Oct 18, 2022
61 | - Add `Output.Id` from `Process.Id`. (https://github.com/CaptnCodr/Fli/pull/27)
62 | - Add `WSL` to provided `Shells`. (https://github.com/CaptnCodr/Fli/pull/31)
63 | - Enhencement: Trim output texts at the end. (https://github.com/CaptnCodr/Fli/pull/32)
64 | - Add logo for Nuget package. (https://github.com/CaptnCodr/Fli/pull/34)
65 | - Update dependencies.
66 |
67 | v0.8.0.0 - Oct 12, 2022
68 | - Add `Command.executeAsync` for `NET5` and up! (https://github.com/CaptnCodr/Fli/pull/19)
69 | - Add `Error` from `StandardError`. (https://github.com/CaptnCodr/Fli/pull/20)
70 | - Add `Input` for `StandardInput`. (https://github.com/CaptnCodr/Fli/pull/21)
71 | - Fix some build warnings. (https://github.com/CaptnCodr/Fli/pull/22)
72 |
73 | v0.7.0.0 - Oct 07, 2022
74 | - Add `Encoding` to contexts. (https://github.com/CaptnCodr/Fli/pull/11)
75 | - Renaming of contexts.
76 | - Update docs with more snippets etc.
77 | - **BREAKING** Add output type with exit code: `type Output = { Text: string; ExitCode: int }` (https://github.com/CaptnCodr/Fli/pull/14)
78 |
79 | v0.6.1.0 - Oct 04, 2022
80 | - Fix: Wrong (old) content in Release 0.6.0 Nuget package.
81 |
82 | v0.6.0.0 - Oct 04, 2022
83 | - Add `WorkingDirectory` to both ShellContext & ProgramContext. (https://github.com/CaptnCodr/Fli/pull/4)
84 | - Add `Verb` to ProgramContext. (https://github.com/CaptnCodr/Fli/pull/6)
85 | - Add `Username` to ProgramContext. (https://github.com/CaptnCodr/Fli/pull/5)
86 | + `Credentials` added later (for windows only)
87 | - Add `EnvironmentVariables` to contexts. (https://github.com/CaptnCodr/Fli/pull/8)
88 | - Add internal method `configureProcess` for better testing. (https://github.com/CaptnCodr/Fli/pull/9)
89 |
90 | v0.0.2.0 - "Hello World!"-Release Sep 29, 2022
91 | - Initial release
--------------------------------------------------------------------------------
/build.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget: Fli"
2 |
3 | open Fli
4 |
5 | // Empty output directory
6 | cli {
7 | Shell PWSH
8 | Command "Remove-Item * -Include *.nupkg"
9 | WorkingDirectory "src/.nupkg/"
10 | }
11 | |> Command.execute
12 | |> Output.toText
13 | |> printfn "%s"
14 |
15 | // Build in Release configuration
16 | cli {
17 | Exec "dotnet"
18 | Arguments [| "build"; "-c"; "Release" |]
19 | WorkingDirectory "src/"
20 | }
21 | |> Command.execute
22 | |> Output.throwIfErrored
23 | |> Output.toText
24 | |> printfn "%s"
25 |
26 | // Pack Nuget Package
27 | cli {
28 | Shell CMD
29 | Command "paket pack .nupkg"
30 | WorkingDirectory "src/"
31 | }
32 | |> Command.execute
33 | |> Output.throwIfErrored
34 | |> Output.toText
35 | |> printfn "%s"
36 |
37 | // Push Nuget Package
38 | cli {
39 | Shell PWSH
40 | Command "paket push (Get-Item * -Include *.nupkg).Name"
41 | WorkingDirectory "src/.nupkg/"
42 | }
43 | |> Command.execute
44 | |> Output.toText
45 | |> printfn "%s"
46 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptnCodr/Fli/334b1bc12456bae53cc59db22dd23ed9434343a9/logo.png
--------------------------------------------------------------------------------
/paket.dependencies:
--------------------------------------------------------------------------------
1 | source https://api.nuget.org/v3/index.json
2 | framework: net6.0, net7.0, net8.0, netstandard2.0, netstandard2.1
3 |
4 | nuget FSharp.Core ~> 6.0.0
5 |
6 | nuget FsUnit
7 | nuget Microsoft.NET.Test.Sdk
8 | nuget NUnit
9 | nuget NUnit3TestAdapter
10 | nuget NUnit.Analyzers
--------------------------------------------------------------------------------
/paket.lock:
--------------------------------------------------------------------------------
1 | RESTRICTION: || (== net6.0) (== net7.0) (== net8.0) (== netstandard2.0) (== netstandard2.1)
2 | NUGET
3 | remote: https://api.nuget.org/v3/index.json
4 | FSharp.Core (6.0.7)
5 | FsUnit (7.0.1)
6 | FSharp.Core (>= 5.0.2)
7 | NUnit (>= 4.0.1)
8 | Microsoft.ApplicationInsights (2.23) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
9 | System.Diagnostics.DiagnosticSource (>= 5.0)
10 | Microsoft.CodeCoverage (17.13) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
11 | Microsoft.NET.Test.Sdk (17.13)
12 | Microsoft.CodeCoverage (>= 17.13) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
13 | Microsoft.TestPlatform.TestHost (>= 17.13) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
14 | Microsoft.Testing.Extensions.Telemetry (1.6.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
15 | Microsoft.ApplicationInsights (>= 2.22)
16 | Microsoft.Testing.Platform (>= 1.6.3)
17 | Microsoft.Testing.Extensions.TrxReport.Abstractions (1.6.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
18 | Microsoft.Testing.Platform (>= 1.6.3)
19 | Microsoft.Testing.Extensions.VSTestBridge (1.6.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
20 | Microsoft.Testing.Extensions.Telemetry (>= 1.6.3)
21 | Microsoft.Testing.Extensions.TrxReport.Abstractions (>= 1.6.3)
22 | Microsoft.Testing.Platform (>= 1.6.3)
23 | Microsoft.TestPlatform.ObjectModel (>= 17.13)
24 | Microsoft.Testing.Platform (1.6.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
25 | Microsoft.Testing.Platform.MSBuild (1.6.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
26 | Microsoft.Testing.Platform (>= 1.6.3)
27 | Microsoft.TestPlatform.ObjectModel (17.13) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
28 | System.Reflection.Metadata (>= 1.6)
29 | Microsoft.TestPlatform.TestHost (17.13) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
30 | Microsoft.TestPlatform.ObjectModel (>= 17.13) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
31 | Newtonsoft.Json (>= 13.0.1) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
32 | Newtonsoft.Json (13.0.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
33 | NUnit (4.3.2)
34 | NUnit.Analyzers (4.7)
35 | NUnit3TestAdapter (5.0)
36 | Microsoft.Testing.Extensions.VSTestBridge (>= 1.5.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
37 | Microsoft.Testing.Platform.MSBuild (>= 1.5.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
38 | System.Collections.Immutable (9.0.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
39 | System.Memory (>= 4.5.5) - restriction: || (== net6.0) (== net7.0) (&& (== net8.0) (>= net462)) (== netstandard2.0) (== netstandard2.1)
40 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (== net6.0) (== net7.0) (&& (== net8.0) (>= net462)) (== netstandard2.0) (== netstandard2.1)
41 | System.Diagnostics.DiagnosticSource (9.0.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= net462)) (&& (== netstandard2.1) (>= netcoreapp3.1))
42 | System.Memory (>= 4.5.5) - restriction: || (== net6.0) (== net7.0) (&& (== net8.0) (>= net462)) (== netstandard2.0) (== netstandard2.1)
43 | System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (== net6.0) (== net7.0) (&& (== net8.0) (>= net462)) (== netstandard2.0) (== netstandard2.1)
44 | System.Memory (4.6.3) - restriction: || (== net6.0) (== net7.0) (&& (== net8.0) (== netstandard2.0)) (&& (== net8.0) (== netstandard2.1)) (&& (== net8.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
45 | System.Reflection.Metadata (9.0.3) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
46 | System.Collections.Immutable (>= 9.0.3)
47 | System.Memory (>= 4.5.5) - restriction: || (== net6.0) (== net7.0) (&& (== net8.0) (>= net462)) (== netstandard2.0) (== netstandard2.1)
48 | System.Runtime.CompilerServices.Unsafe (6.1.2) - restriction: || (== net6.0) (== net7.0) (&& (== net8.0) (== netstandard2.0)) (&& (== net8.0) (== netstandard2.1)) (&& (== net8.0) (>= net462)) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1))
49 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ExecContext/ExecCommandConfigureUnixTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ExecContext.ExecCommandConfigureUnixTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 | open System.Collections.Generic
8 | open System.Text
9 |
10 | []
11 | []
12 | let ``Check FileName in ProcessStartInfo Exec program`` () =
13 | cli { Exec "bash" } |> Command.buildProcess |> _.FileName |> should equal "bash"
14 |
15 | []
16 | []
17 | let ``Check Arguments in ProcessStartInfo with Arguments`` () =
18 | cli {
19 | Exec "bash"
20 | Arguments "-c echo Hello World!"
21 | }
22 | |> Command.buildProcess
23 | |> _.Arguments
24 | |> should equal "-c echo Hello World!"
25 |
26 | []
27 | []
28 | let ``Check WorkingDirectory in ProcessStartInfo with WorkingDirectory`` () =
29 | cli {
30 | Exec "bash"
31 | WorkingDirectory @"/etc/"
32 | }
33 | |> Command.buildProcess
34 | |> _.WorkingDirectory
35 | |> should equal @"/etc/"
36 |
37 | []
38 | []
39 | let ``Check WindowStyle in ProcessStartInfo with WorkingDirectory`` () =
40 | cli {
41 | Exec "bash"
42 | WindowStyle Normal
43 | }
44 | |> Command.buildProcess
45 | |> _.WindowStyle
46 | |> should equal Diagnostics.ProcessWindowStyle.Normal
47 |
48 | []
49 | []
50 | let ``Check UserName in ProcessStartInfo with Username`` () =
51 | cli {
52 | Exec "bash"
53 | Username "root"
54 | }
55 | |> Command.buildProcess
56 | |> _.UserName
57 | |> should equal "root"
58 |
59 | []
60 | []
61 | let ``Check Environment in ProcessStartInfo with single environment variable`` () =
62 | cli {
63 | Exec "bash"
64 | EnvironmentVariable("Fli", "test")
65 | }
66 | |> Command.buildProcess
67 | |> _.Environment.Contains(KeyValuePair("Fli", "test"))
68 | |> should be True
69 |
70 | []
71 | []
72 | let ``Check Environment in ProcessStartInfo with multiple environment variables`` () =
73 | let config =
74 | cli {
75 | Exec "bash"
76 | EnvironmentVariables [ ("Fli", "test"); ("Fli.Test", "test") ]
77 | }
78 | |> Command.buildProcess
79 |
80 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
81 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
82 |
83 | []
84 | []
85 | let ``Check StandardOutputEncoding & StandardErrorEncoding with setting Encoding`` () =
86 | let config =
87 | cli {
88 | Exec "bash"
89 | Encoding Encoding.UTF8
90 | }
91 | |> Command.buildProcess
92 |
93 | config.StandardOutputEncoding |> should equal Encoding.UTF8
94 | config.StandardErrorEncoding |> should equal Encoding.UTF8
95 |
96 | []
97 | []
98 | let ``Check all possible values in ProcessStartInfo`` () =
99 | let config =
100 | cli {
101 | Exec "bash"
102 | Arguments "--help"
103 | Output "./Users/test.txt"
104 | WorkingDirectory "./Users"
105 | Username "admin"
106 | EnvironmentVariable("Fli", "test")
107 | EnvironmentVariables [ ("Fli.Test", "test") ]
108 | Encoding Encoding.UTF8
109 | }
110 | |> Command.buildProcess
111 |
112 | config.FileName |> should equal "bash"
113 | config.Arguments |> should equal "--help"
114 | config.WorkingDirectory |> should equal "./Users"
115 | config.Verb |> should equal String.Empty
116 | config.UserName |> should equal "admin"
117 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
118 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
119 | config.StandardOutputEncoding |> should equal Encoding.UTF8
120 | config.StandardErrorEncoding |> should equal Encoding.UTF8
121 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ExecContext/ExecCommandConfigureWindowsTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ExecContext.ExecCommandConfigureWindowsTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 | open System.Collections.Generic
8 | open System.Text
9 |
10 | []
11 | []
12 | let ``Check FileName in ProcessStartInfo Exec program`` () =
13 | cli { Exec "cmd.exe" }
14 | |> Command.buildProcess
15 | |> _.FileName
16 | |> should equal "cmd.exe"
17 |
18 | []
19 | []
20 | let ``Check Arguments in ProcessStartInfo with Arguments`` () =
21 | cli {
22 | Exec "cmd.exe"
23 | Arguments "-c echo Hello World!"
24 | }
25 | |> Command.buildProcess
26 | |> _.Arguments
27 | |> should equal "-c echo Hello World!"
28 |
29 | #if NET
30 | []
31 | []
32 | let ``Check Arguments in ProcessStartInfo with ArgumentList`` () =
33 | cli {
34 | Exec "cmd.exe"
35 | Arguments [ "-c"; "echo Hello World!" ]
36 | }
37 | |> Command.buildProcess
38 | |> _.ArgumentList
39 | :> seq<_>
40 | |> should
41 | equal
42 | (seq {
43 | "-c"
44 | "echo Hello World!"
45 | })
46 | #endif
47 |
48 | []
49 | []
50 | let ``Check WorkingDirectory in ProcessStartInfo with WorkingDirectory`` () =
51 | cli {
52 | Exec "cmd.exe"
53 | WorkingDirectory @"C:\Users"
54 | }
55 | |> Command.buildProcess
56 | |> _.WorkingDirectory
57 | |> should equal @"C:\Users"
58 |
59 | []
60 | []
61 | let ``Check WindowStyle in ProcessStartInfo with WorkingDirectory`` () =
62 | cli {
63 | Exec "cmd.exe"
64 | WindowStyle Normal
65 | }
66 | |> Command.buildProcess
67 | |> _.WindowStyle
68 | |> should equal Diagnostics.ProcessWindowStyle.Normal
69 |
70 | []
71 | []
72 | let ``Check Verb in ProcessStartInfo with Verb`` () =
73 | cli {
74 | Exec "cmd.exe"
75 | Verb "open"
76 | }
77 | |> Command.buildProcess
78 | |> _.Verb
79 | |> should equal "open"
80 |
81 | []
82 | []
83 | let ``Check UserName in ProcessStartInfo with Username`` () =
84 | cli {
85 | Exec "cmd.exe"
86 | Username "admin"
87 | }
88 | |> Command.buildProcess
89 | |> _.UserName
90 | |> should equal "admin"
91 |
92 | []
93 | []
94 | let ``Check Domain, UserName, Password in ProcessStartInfo with Credentials for windows (overwrites Username)`` () =
95 | let config =
96 | cli {
97 | Exec "cmd.exe"
98 | Username "admin"
99 | Credentials("domain", "user", "password")
100 | }
101 | |> Command.buildProcess
102 |
103 | config.Domain |> should equal "domain"
104 | config.UserName |> should equal "user"
105 | config.Password |> should not' (equal "password") // stored as SecureString
106 |
107 | []
108 | []
109 | let ``Check Environment in ProcessStartInfo with single environment variable`` () =
110 | cli {
111 | Exec "cmd.exe"
112 | EnvironmentVariable("Fli", "test")
113 | }
114 | |> Command.buildProcess
115 | |> _.Environment.Contains(KeyValuePair("Fli", "test"))
116 | |> should be True
117 |
118 | []
119 | []
120 | let ``Check Environment in ProcessStartInfo with multiple environment variables`` () =
121 | let config =
122 | cli {
123 | Exec "cmd.exe"
124 | EnvironmentVariables [ ("Fli", "test"); ("Fli.Test", "test") ]
125 | }
126 | |> Command.buildProcess
127 |
128 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
129 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
130 |
131 | []
132 | []
133 | let ``Check StandardOutputEncoding & StandardErrorEncoding with setting Encoding`` () =
134 | let config =
135 | cli {
136 | Exec "cmd.exe"
137 | Encoding Encoding.UTF8
138 | }
139 | |> Command.buildProcess
140 |
141 | config.StandardOutputEncoding |> should equal Encoding.UTF8
142 | config.StandardErrorEncoding |> should equal Encoding.UTF8
143 |
144 | []
145 | []
146 | let ``Check all possible values in ProcessStartInfo for windows`` () =
147 | let config =
148 | cli {
149 | Exec "cmd.exe"
150 | Arguments "--help"
151 | Input "Test"
152 | Output @"C:\Users\test.txt"
153 | WorkingDirectory @"C:\Users"
154 | Verb "open"
155 | Username "admin"
156 | Credentials("domain", "admin", "password")
157 | EnvironmentVariable("Fli", "test")
158 | EnvironmentVariables [ ("Fli.Test", "test") ]
159 | Encoding Encoding.UTF8
160 | WindowStyle Normal
161 | }
162 | |> Command.buildProcess
163 |
164 | config.FileName |> should equal "cmd.exe"
165 | config.Arguments |> should equal "--help"
166 | config.WorkingDirectory |> should equal @"C:\Users"
167 | config.Verb |> should equal "open"
168 | config.Domain |> should equal "domain"
169 | config.UserName |> should equal "admin"
170 | config.Password |> should not' (equal "password")
171 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
172 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
173 | config.StandardOutputEncoding |> should equal Encoding.UTF8
174 | config.StandardErrorEncoding |> should equal Encoding.UTF8
175 | config.WindowStyle |> should equal Diagnostics.ProcessWindowStyle.Normal
176 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ExecContext/ExecCommandExecuteUnixTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ExecContext.ExecCommandExecuteLinuxTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 | open System.Text
8 |
9 | []
10 | []
11 | let ``Hello World with executing program`` () =
12 | cli {
13 | Exec "bash"
14 | Arguments "-c \"echo Hello World!\""
15 | }
16 | |> Command.execute
17 | |> Output.toText
18 | |> should equal "Hello World!"
19 |
20 | []
21 | []
22 | let ``Get process Id`` () =
23 | cli {
24 | Exec "bash"
25 | Arguments "-c \"echo Test\""
26 | }
27 | |> Command.execute
28 | |> Output.toId
29 | |> should not' (equal 0)
30 |
31 | []
32 | []
33 | let ``print text with Input with executing program`` () =
34 | cli {
35 | Exec "bash"
36 | Arguments "-c \"echo Test\""
37 | Input "echo Hello World!"
38 | WorkingDirectory @"/etc/"
39 | }
40 | |> Command.execute
41 | |> Output.toText
42 | |> should equal "Test"
43 |
44 | []
45 | []
46 | let ``Get output in StringBuilder`` () =
47 | let sb = StringBuilder()
48 |
49 | cli {
50 | Exec "bash"
51 | Arguments "-c \"echo Test\""
52 | Output sb
53 | }
54 | |> Command.execute
55 | |> ignore
56 |
57 | sb.ToString() |> should equal "Test\n"
58 |
59 | []
60 | []
61 | let ``Call custom function in output`` () =
62 | let testFunc (test: string) (s: string) = s |> should equal test
63 |
64 | cli {
65 | Exec "bash"
66 | Arguments "-c \"echo Test\""
67 | Output(testFunc "Test\n")
68 | }
69 | |> Command.execute
70 | |> ignore
71 |
72 | []
73 | []
74 | let ``Hello World with executing program async`` () =
75 | async {
76 | let! output =
77 | cli {
78 | Exec "bash"
79 | Arguments "-c \"echo Hello World!\""
80 | }
81 | |> Command.executeAsync
82 |
83 | output |> Output.toText |> should equal "Hello World!"
84 | }
85 |
86 | []
87 | []
88 | let ``Passing data to a progrma on stdin`` () =
89 | cli {
90 | Exec "cat"
91 | Input "Test"
92 | }
93 | |> Command.execute
94 | |> Output.toText
95 | |> should equal "Test"
96 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ExecContext/ExecCommandExecuteWindowsTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ExecContext.ExecCommandExecuteWindowsTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 | open System.Text
8 |
9 |
10 | []
11 | []
12 | let ``Hello World with executing program`` () =
13 | cli {
14 | Exec "cmd.exe"
15 | Arguments "/C echo Hello World!"
16 | }
17 | |> Command.execute
18 | |> Output.toText
19 | |> should equal "Hello World!"
20 |
21 | []
22 | []
23 | let ``Print "Test" properly with ArgumentList`` () =
24 | cli {
25 | Exec "pwsh.exe"
26 | Arguments [ "/C"; "Write-Host \"Test\"" ]
27 | }
28 | |> Command.execute
29 | |> Output.toText
30 | |> should equal "Test"
31 |
32 | []
33 | []
34 | let ``Get process Id`` () =
35 | cli {
36 | Exec "cmd.exe"
37 | Arguments "/C echo Test"
38 | }
39 | |> Command.execute
40 | |> Output.toId
41 | |> should not' (equal 0)
42 |
43 | []
44 | []
45 | let ``print text with Input with executing program`` () =
46 | cli {
47 | Exec "cmd.exe"
48 | Arguments "/k echo Test"
49 | Input "echo Hello World!"
50 | WorkingDirectory @"C:\"
51 | }
52 | |> Command.execute
53 | |> Output.toText
54 | |> should equal "Test\r\n\r\nC:\\>echo Hello World!\r\nHello World!\r\n\r\nC:\\>"
55 |
56 | []
57 | []
58 | let ``Get output in StringBuilder`` () =
59 | let sb = StringBuilder()
60 |
61 | cli {
62 | Exec "cmd.exe"
63 | Arguments "/c echo Test"
64 | Output sb
65 | }
66 | |> Command.execute
67 | |> ignore
68 |
69 | sb.ToString() |> should equal "Test\r\n"
70 |
71 | []
72 | []
73 | let ``Call custom function in output`` () =
74 | let testFunc (test: string) (s: string) = s |> should equal test
75 |
76 | cli {
77 | Exec "cmd.exe"
78 | Arguments "/c echo Test"
79 | Output(testFunc "Test\r\n")
80 | }
81 | |> Command.execute
82 | |> ignore
83 |
84 | []
85 | []
86 | let ``Hello World with executing program async`` () =
87 | async {
88 | let! output =
89 | cli {
90 | Exec "cmd.exe"
91 | Arguments "/C echo Hello World!"
92 | }
93 | |> Command.executeAsync
94 |
95 | output |> Output.toText |> should equal "Hello World!"
96 | }
97 |
98 |
99 | []
100 | []
101 | let ``Hello World with executing program with Verb`` () =
102 | cli {
103 | Exec "cmd.exe"
104 | Verb "open"
105 | Arguments "/C echo Hello World!"
106 | }
107 | |> Command.execute
108 | |> Output.toText
109 | |> should equal "Hello World!"
110 |
111 |
112 | []
113 | []
114 | let ``Hello World with executing program throws exception with unknown Verb`` () =
115 | try
116 | cli {
117 | Exec "cmd.exe"
118 | Verb "print"
119 | }
120 | |> Command.execute
121 | |> ignore
122 | with :? ArgumentException as ex ->
123 | ex.Message
124 | |> should equal ("Unknown verb 'print'. Possible verbs on 'cmd.exe': open, runas, runasuser")
125 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ExecContext/ExecCommandToStringTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ExecContext.ExecCommandToStringTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 |
7 |
8 | []
9 | let ``Hello World with executing program`` () =
10 | cli {
11 | Exec "cmd.exe"
12 | Arguments "/C echo Hello World!"
13 | }
14 | |> Command.toString
15 | |> should equal "cmd.exe /C echo Hello World!"
16 |
17 | []
18 | let ``Hello World with an argument list`` () =
19 | cli {
20 | Exec "cmd.exe"
21 | Arguments [ "/C"; "echo"; "Hello World!" ]
22 | }
23 | |> Command.toString
24 | |> should equal "cmd.exe /C echo Hello World!"
25 |
26 | []
27 | let ``Hello World with an argument array`` () =
28 | cli {
29 | Exec "cmd.exe"
30 | Arguments [| "/C"; "echo"; "Hello World!" |]
31 | }
32 | |> Command.toString
33 | |> should equal "cmd.exe /C echo Hello World!"
34 |
35 | []
36 | let ``Hello World with an argument seq`` () =
37 | cli {
38 | Exec "cmd.exe"
39 |
40 | Arguments(
41 | seq {
42 | "/C"
43 | "echo"
44 | "Hello World!"
45 | }
46 | )
47 | }
48 | |> Command.toString
49 | |> should equal "cmd.exe /C echo Hello World!"
50 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ExecContext/ExecConfigTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ExecContext.ExecCommandConfigTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 |
7 |
8 | []
9 | let ``Check executable name config for executing program`` () =
10 | cli { Exec "cmd.exe" } |> (fun c -> c.config.Program) |> should equal "cmd.exe"
11 |
12 | []
13 | let ``Check arguments config for executing program`` () =
14 | cli {
15 | Exec "cmd.exe"
16 | Arguments "echo Hello World!"
17 | }
18 | |> _.config.Arguments
19 | |> should equal (Some(Arguments(Some "echo Hello World!")))
20 |
21 | []
22 | let ``Check arguments list config for executing program`` () =
23 | cli {
24 | Exec "cmd.exe"
25 | Arguments [ "echo"; "Hello World!" ]
26 | }
27 | |> _.config.Arguments
28 | |> should equal (Some(ArgumentList(Some [| "echo"; "Hello World!" |])))
29 |
30 | []
31 | let ``Check Input config for executing program`` () =
32 | cli {
33 | Exec "cmd.exe"
34 | Input "echo 123\r\necho Test"
35 | }
36 | |> _.config.Input
37 | |> should equal (Some "echo 123\r\necho Test")
38 |
39 | []
40 | let ``Check Output config for executing program`` () =
41 | cli {
42 | Exec "cmd.exe"
43 | Output @"C:\Users\test.txt"
44 | }
45 | |> _.config.Output
46 | |> should equal (Some(File @"C:\Users\test.txt"))
47 |
48 | []
49 | let ``Empty string in Output ends up as None`` () =
50 | cli {
51 | Exec "cmd.exe"
52 | Output ""
53 | }
54 | |> _.config.Output
55 | |> should be (ofCase <@ None @>)
56 |
57 | []
58 | let ``Nullable file path in Output ends up as None`` () =
59 | cli {
60 | Exec "cmd.exe"
61 | Output(File(null))
62 | }
63 | |> _.config.Output
64 | |> should be (ofCase <@ None @>)
65 |
66 | []
67 | let ``Check working directory config for executing program`` () =
68 | cli {
69 | Exec "cmd.exe"
70 | WorkingDirectory @"C:\Users"
71 | }
72 | |> _.config.WorkingDirectory
73 | |> should equal (Some @"C:\Users")
74 |
75 | []
76 | let ``Check WindowStyle config for executing program`` () =
77 | cli {
78 | Exec "cmd.exe"
79 | WindowStyle Normal
80 | }
81 | |> _.config.WindowStyle
82 | |> should equal (Some Normal)
83 |
84 | []
85 | let ``Check Verb config for executing program`` () =
86 | cli {
87 | Exec "cmd.exe"
88 | Verb "runas"
89 | }
90 | |> _.config.Verb
91 | |> should equal (Some "runas")
92 |
93 | []
94 | let ``Check credentials config for executing program`` () =
95 | cli {
96 | Exec "cmd.exe"
97 | WorkingDirectory @"C:\Users"
98 | Username "admin"
99 | }
100 | |> _.config.UserName.Value
101 | |> should equal "admin"
102 |
103 | []
104 | let ``Check credentials config for executing program with NetworkCredentials`` () =
105 | cli {
106 | Exec "cmd.exe"
107 | WorkingDirectory @"C:\Users"
108 | Username "admin@company"
109 | }
110 | |> _.config.UserName.Value
111 | |> should equal "admin@company"
112 |
113 | []
114 | let ``Check EnvironmentVariables with single KeyValue config`` () =
115 | cli {
116 | Exec "cmd.exe"
117 | EnvironmentVariable("user", "admin")
118 | }
119 | |> _.config.EnvironmentVariables.Value
120 | |> should equal [ ("user", "admin") ]
121 |
122 | []
123 | let ``Check EnvironmentVariables with multiple KeyValues config`` () =
124 | cli {
125 | Exec "cmd.exe"
126 | EnvironmentVariables [ ("user", "admin"); ("path", "path/to/file") ]
127 | }
128 | |> _.config.EnvironmentVariables.Value
129 | |> should equal [ ("user", "admin"); ("path", "path/to/file") ]
130 |
131 | []
132 | let ``Check Credentials with domain, username and password`` () =
133 | cli {
134 | Exec "cmd.exe"
135 | Credentials("domain", "user", "password123")
136 | }
137 | |> _.config.Credentials.Value
138 | |> should equal (Credentials("domain", "user", "password123"))
139 |
140 | []
141 | let ``Check Encoding with setting Encoding`` () =
142 | cli {
143 | Exec "cmd.exe"
144 | Encoding System.Text.Encoding.UTF8
145 | }
146 | |> _.config.Encoding.Value
147 | |> should equal System.Text.Encoding.UTF8
148 |
--------------------------------------------------------------------------------
/src/Fli.Tests/Fli.Tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 | net8.0
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ShellContext/ShellCommandConfigureUnixTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ShellContext.ShellCommandConfigureUnixTests
2 |
3 | open Fli
4 | open NUnit.Framework
5 | open FsUnit
6 | open System.Collections.Generic
7 | open System.Text
8 |
9 |
10 | []
11 | []
12 | let ``Check FileName in ProcessStartInfo with CMD Shell`` () =
13 | cli { Shell BASH } |> Command.buildProcess |> _.FileName |> should equal "bash"
14 |
15 | []
16 | []
17 | let ``Check Argument in ProcessStartInfo with Command`` () =
18 | cli {
19 | Shell BASH
20 | Command "echo Hello World!"
21 | }
22 | |> Command.buildProcess
23 | |> _.Arguments
24 | |> should equal "-c \"echo Hello World!\""
25 |
26 | []
27 | []
28 | let ``Check WorkingDirectory in ProcessStartInfo with WorkingDirectory`` () =
29 | cli {
30 | Shell BASH
31 | WorkingDirectory @"/etc/"
32 | }
33 | |> Command.buildProcess
34 | |> _.WorkingDirectory
35 | |> should equal @"/etc/"
36 |
37 | []
38 | []
39 | let ``Check Environment in ProcessStartInfo with single environment variable`` () =
40 | cli {
41 | Shell BASH
42 | EnvironmentVariable("Fli", "test")
43 | }
44 | |> Command.buildProcess
45 | |> _.Environment.Contains(KeyValuePair("Fli", "test"))
46 | |> should be True
47 |
48 | []
49 | []
50 | let ``Check Environment in ProcessStartInfo with multiple environment variables`` () =
51 | let config =
52 | cli {
53 | Shell BASH
54 | EnvironmentVariables [ ("Fli", "test"); ("Fli.Test", "test") ]
55 | }
56 | |> Command.buildProcess
57 |
58 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
59 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
60 |
61 | []
62 | []
63 | let ``Check StandardOutputEncoding & StandardErrorEncoding with setting Encoding`` () =
64 | let config =
65 | cli {
66 | Shell BASH
67 | Encoding Encoding.UTF8
68 | }
69 | |> Command.buildProcess
70 |
71 | config.StandardOutputEncoding |> should equal Encoding.UTF8
72 | config.StandardErrorEncoding |> should equal Encoding.UTF8
73 |
74 | []
75 | []
76 | let ``Check all possible values in ProcessStartInfo`` () =
77 | let config =
78 | cli {
79 | Shell BASH
80 | Command "echo Hello World! €"
81 | Input "echo TestInput"
82 | WorkingDirectory @"C:\Users"
83 | EnvironmentVariable("Fli", "test")
84 | EnvironmentVariables [ ("Fli.Test", "test") ]
85 | Encoding Encoding.UTF8
86 | }
87 | |> Command.buildProcess
88 |
89 | config.FileName |> should equal "bash"
90 | config.Arguments |> should equal "-c \"echo Hello World! €\""
91 | config.WorkingDirectory |> should equal @"C:\Users"
92 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
93 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
94 | config.StandardOutputEncoding |> should equal Encoding.UTF8
95 | config.StandardErrorEncoding |> should equal Encoding.UTF8
96 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ShellContext/ShellCommandConfigureWindowsTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ShellContext.ShellCommandConfigureWindowsTests
2 |
3 | open Fli
4 | open NUnit.Framework
5 | open FsUnit
6 | open System.Collections.Generic
7 | open System.Text
8 |
9 |
10 | []
11 | []
12 | let ``Check FileName in ProcessStartInfo with CMD Shell`` () =
13 | cli { Shell CMD }
14 | |> Command.buildProcess
15 | |> _.FileName
16 | |> should equal "cmd.exe"
17 |
18 | []
19 | []
20 | let ``Check Argument in ProcessStartInfo with Command`` () =
21 | cli {
22 | Shell PS
23 | Command "echo Hello World!"
24 | }
25 | |> Command.buildProcess
26 | |> _.Arguments
27 | |> should equal "-Command echo Hello World!"
28 |
29 | []
30 | []
31 | let ``Check WorkingDirectory in ProcessStartInfo with WorkingDirectory`` () =
32 | cli {
33 | Shell CMD
34 | WorkingDirectory @"C:\Users"
35 | }
36 | |> Command.buildProcess
37 | |> _.WorkingDirectory
38 | |> should equal @"C:\Users"
39 |
40 | []
41 | []
42 | let ``Check Environment in ProcessStartInfo with single environment variable`` () =
43 | cli {
44 | Shell CMD
45 | EnvironmentVariable("Fli", "test")
46 | }
47 | |> Command.buildProcess
48 | |> _.Environment.Contains(KeyValuePair("Fli", "test"))
49 | |> should be True
50 |
51 | []
52 | []
53 | let ``Check Environment in ProcessStartInfo with multiple environment variables`` () =
54 | let config =
55 | cli {
56 | Shell CMD
57 | EnvironmentVariables [ ("Fli", "test"); ("Fli.Test", "test") ]
58 | }
59 | |> Command.buildProcess
60 |
61 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
62 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
63 |
64 | []
65 | []
66 | let ``Check StandardOutputEncoding & StandardErrorEncoding with setting Encoding`` () =
67 | let config =
68 | cli {
69 | Shell CMD
70 | Encoding Encoding.UTF8
71 | }
72 | |> Command.buildProcess
73 |
74 | config.StandardOutputEncoding |> should equal Encoding.UTF8
75 | config.StandardErrorEncoding |> should equal Encoding.UTF8
76 |
77 | []
78 | []
79 | let ``Check all possible values in ProcessStartInfo`` () =
80 | let config =
81 | cli {
82 | Shell CMD
83 | Command "echo Hello World! €"
84 | Input "echo TestInput"
85 | WorkingDirectory @"C:\Users"
86 | EnvironmentVariable("Fli", "test")
87 | EnvironmentVariables [ ("Fli.Test", "test") ]
88 | Encoding Encoding.UTF8
89 | }
90 | |> Command.buildProcess
91 |
92 | config.FileName |> should equal "cmd.exe"
93 | config.Arguments |> should equal "/k echo Hello World! €"
94 | config.WorkingDirectory |> should equal @"C:\Users"
95 | config.Environment.Contains(KeyValuePair("Fli", "test")) |> should be True
96 | config.Environment.Contains(KeyValuePair("Fli.Test", "test")) |> should be True
97 | config.StandardOutputEncoding |> should equal Encoding.UTF8
98 | config.StandardErrorEncoding |> should equal Encoding.UTF8
99 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ShellContext/ShellCommandExecuteUnixTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ShellContext.ShellCommandExecuteUnixTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 | open System.Text
8 | open System.Diagnostics
9 |
10 | []
11 | []
12 | let ``Hello World with CUSTOM shell`` () =
13 | cli {
14 | Shell(CUSTOM("bash", "-c"))
15 | Command "echo Hello World!"
16 | }
17 | |> Command.execute
18 | |> Output.toText
19 | |> should equal "Hello World!"
20 |
21 | []
22 | []
23 | let ``Get output in StringBuilder`` () =
24 | let sb = StringBuilder()
25 |
26 | cli {
27 | Shell BASH
28 | Command "echo Test"
29 | Output sb
30 | }
31 | |> Command.execute
32 | |> ignore
33 |
34 | sb.ToString() |> should equal "Test\n"
35 |
36 | []
37 | []
38 | let ``Hello World with SH`` () =
39 | cli {
40 | Shell SH
41 | Command "echo Hello World!"
42 | }
43 | |> Command.execute
44 | |> Output.toText
45 | |> should equal "Hello World!"
46 |
47 | []
48 | []
49 | let ``Hello World with BASH`` () =
50 | cli {
51 | Shell BASH
52 | Command "echo Hello World!"
53 | }
54 | |> Command.execute
55 | |> Output.toText
56 | |> should equal "Hello World!"
57 |
58 | []
59 | []
60 | let ``Hello World with ZSH`` () =
61 | cli {
62 | Shell ZSH
63 | Command "echo Hello World!"
64 | }
65 | |> Command.execute
66 | |> Output.toText
67 | |> should equal "Hello World!"
68 |
69 | []
70 | []
71 | let ``Input text in BASH`` () =
72 | cli {
73 | Shell BASH
74 | Command "echo Hello World!"
75 | Input "echo Test"
76 | }
77 | |> Command.execute
78 | |> Output.toText
79 | |> should equal "Hello World!"
80 |
81 | []
82 | []
83 | let ``Hello World with BASH async`` () =
84 | async {
85 | let! output =
86 | cli {
87 | Shell BASH
88 | Command "echo Hello World!"
89 | }
90 | |> Command.executeAsync
91 |
92 | output |> Output.toText |> should equal "Hello World!"
93 | }
94 |
95 | []
96 | []
97 | let ``BASH returning non zero ExitCode`` () =
98 | cli {
99 | Shell BASH
100 | Command "echl Test"
101 | }
102 | |> Command.execute
103 | |> Output.toExitCode
104 | |> should equal 127
105 |
106 | []
107 | []
108 | let ``BASH returning non zero process id`` () =
109 | cli {
110 | Shell BASH
111 | Command "echo Test"
112 | }
113 | |> Command.execute
114 | |> Output.toId
115 | |> should not' (equal 0)
116 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ShellContext/ShellCommandExecuteWindowsTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ShellContext.ShellCommandExecuteWindowsTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 | open System.Text
8 | open System.Diagnostics
9 |
10 |
11 | []
12 | []
13 | let ``Hello World with CMD`` () =
14 | let operation =
15 | cli {
16 | Shell CMD
17 | Command "echo Hello World!"
18 | }
19 | |> Command.execute
20 |
21 | operation |> Output.toText |> should equal "Hello World!"
22 | operation |> Output.toError |> should equal ""
23 |
24 | []
25 | []
26 | let ``Hello World with CMD waiting async`` () =
27 | async {
28 | let stopwatch = new Stopwatch()
29 | stopwatch.Start()
30 |
31 | try
32 | let! operation =
33 | cli {
34 | Shell(CUSTOM("cmd.exe", "/K"))
35 | Command "Hello World!"
36 | CancelAfter 3000
37 | }
38 | |> Command.executeAsync
39 |
40 | ()
41 | with :? AggregateException as e ->
42 | e.GetType() |> should equal typeof
43 |
44 | stopwatch.Stop()
45 | stopwatch.Elapsed.TotalSeconds |> should be (inRange 2.9 3.2)
46 | }
47 |
48 | []
49 | []
50 | let ``Hello World with CUSTOM shell`` () =
51 | cli {
52 | Shell(CUSTOM("cmd.exe", "/c"))
53 | Command "echo Hello World!"
54 | }
55 | |> Command.execute
56 | |> Output.toText
57 | |> should equal "Hello World!"
58 |
59 | []
60 | []
61 | let ``CMD returning non zero ExitCode`` () =
62 | cli {
63 | Shell CMD
64 | Command "echl Test"
65 | }
66 | |> Command.execute
67 | |> Output.toExitCode
68 | |> should equal 1
69 |
70 | []
71 | []
72 | let ``Get output in StringBuilder`` () =
73 | let sb = StringBuilder()
74 |
75 | cli {
76 | Shell CMD
77 | Command "echo Test"
78 | Output sb
79 | }
80 | |> Command.execute
81 | |> ignore
82 |
83 | sb.ToString() |> should equal "Test\r\n"
84 |
85 | []
86 | []
87 | let ``CMD returning non zero process id`` () =
88 | cli {
89 | Shell CMD
90 | Command "echo Test"
91 | }
92 | |> Command.execute
93 | |> Output.toId
94 | |> should not' (equal 0)
95 |
96 | []
97 | []
98 | let ``Text in Input with CMD`` () =
99 | cli {
100 | Shell CMD
101 | Input "echo 123\r\necho 345"
102 | WorkingDirectory @"C:\"
103 | }
104 | |> Command.execute
105 | |> Output.toText
106 | |> should equal "C:\\>echo 123\r\n123\r\n\r\nC:\\>echo 345\r\n345\r\n\r\nC:\\>"
107 |
108 | []
109 | []
110 | let ``CMD returning error message`` () =
111 | cli {
112 | Shell CMD
113 | Command "echl Test"
114 | }
115 | |> Command.execute
116 | |> Output.toError
117 | |> should not' (equal None)
118 |
119 | []
120 | []
121 | let ``CMD returning error message without text`` () =
122 | let operation =
123 | cli {
124 | Shell CMD
125 | Command "echl Test"
126 | }
127 | |> Command.execute
128 |
129 | operation |> Output.toError |> should not' (equal None)
130 | operation |> Output.toText |> should equal ""
131 |
132 | []
133 | []
134 | let ``Hello World with PS`` () =
135 | cli {
136 | Shell PS
137 | Command "Write-Host Hello World!"
138 | }
139 | |> Command.execute
140 | |> Output.toText
141 | |> should equal "Hello World!"
142 |
143 | []
144 | []
145 | let ``Hello World with PWSH`` () =
146 | cli {
147 | Shell PWSH
148 | Command "Write-Host Hello World!"
149 | }
150 | |> Command.execute
151 | |> Output.toText
152 | |> should equal "Hello World!"
153 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ShellContext/ShellCommandToStringUnixTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ShellContext.ShellCommandToStringUnixTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 |
8 | []
9 | []
10 | let ``PWSH command toString returns full line`` () =
11 | cli {
12 | Shell PWSH
13 | Command "Write-Host Hello World!"
14 | }
15 | |> Command.toString
16 | |> should equal "pwsh -Command Write-Host Hello World!"
17 |
18 | []
19 | []
20 | let ``BASH command toString returns full line`` () =
21 | cli {
22 | Shell BASH
23 | Command "echo Hello World!"
24 | }
25 | |> Command.toString
26 | |> should equal "bash -c \"echo Hello World!\""
27 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ShellContext/ShellCommandToStringWindowsTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ShellContext.ShellCommandToStringWindowsTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 | open System
7 |
8 |
9 | []
10 | []
11 | let ``CMD command toString returns full line`` () =
12 | cli {
13 | Shell CMD
14 | Command "echo Hello World!"
15 | }
16 | |> Command.toString
17 | |> should equal "cmd.exe /c echo Hello World!"
18 |
19 | []
20 | []
21 | let ``CMD command toString returns full line in interactive mode`` () =
22 | cli {
23 | Shell CMD
24 | Command "echo Hello World!"
25 | Input "echo Hello World!"
26 | }
27 | |> Command.toString
28 | |> should equal "cmd.exe /k echo Hello World!"
29 |
30 | []
31 | []
32 | let ``PS command toString returns full line`` () =
33 | cli {
34 | Shell PS
35 | Command "Write-Host Hello World!"
36 | }
37 | |> Command.toString
38 | |> should equal "powershell.exe -Command Write-Host Hello World!"
39 |
40 | []
41 | []
42 | let ``PWSH command toString returns full line`` () =
43 | cli {
44 | Shell PWSH
45 | Command "Write-Host Hello World!"
46 | }
47 | |> Command.toString
48 | |> should equal "pwsh.exe -Command Write-Host Hello World!"
49 |
50 | []
51 | []
52 | let ``WSL command toString returns full line`` () =
53 | cli {
54 | Shell WSL
55 | Command "echo Hello World!"
56 | }
57 | |> Command.toString
58 | |> should equal "wsl.exe -- echo Hello World!"
59 |
--------------------------------------------------------------------------------
/src/Fli.Tests/ShellContext/ShellConfigTests.fs:
--------------------------------------------------------------------------------
1 | module Fli.Tests.ShellContext.ShellConfigTests
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open Fli
6 |
7 |
8 | []
9 | let ``Check Shell config with CMD`` () =
10 | cli { Shell CMD } |> _.config.Shell |> should equal CMD
11 |
12 | []
13 | let ``Check Command config`` () =
14 | cli {
15 | Shell PS
16 | Command "echo test"
17 | }
18 | |> _.config.Command
19 | |> should equal (Some "echo test")
20 |
21 | []
22 | let ``Check Input config for CMD`` () =
23 | cli {
24 | Shell CMD
25 | Input "echo 123\r\necho Test"
26 | }
27 | |> _.config.Input
28 | |> should equal (Some "echo 123\r\necho Test")
29 |
30 | []
31 | let ``Check Output config for CMD`` () =
32 | cli {
33 | Shell CMD
34 | Output @"C:\Users\test.txt"
35 | }
36 | |> _.config.Output
37 | |> should equal (Some(File @"C:\Users\test.txt"))
38 |
39 | []
40 | let ``Empty string in Output ends up as None`` () =
41 | cli {
42 | Shell CMD
43 | Output ""
44 | }
45 | |> _.config.Output
46 | |> should be (ofCase <@ None @>)
47 |
48 | []
49 | let ``Nullable file path in Output ends up as None`` () =
50 | cli {
51 | Shell CMD
52 | Output(File(null))
53 | }
54 | |> _.config.Output
55 | |> should be (ofCase <@ None @>)
56 |
57 | []
58 | let ``Check WorkingDirectory config`` () =
59 | cli {
60 | Shell BASH
61 | WorkingDirectory @"C:\Users"
62 | }
63 | |> _.config.WorkingDirectory
64 | |> should equal (Some @"C:\Users")
65 |
66 | []
67 | let ``Check EnvironmentVariables with single KeyValue config`` () =
68 | cli {
69 | Shell BASH
70 | EnvironmentVariable("user", "admin")
71 | }
72 | |> _.config.EnvironmentVariables.Value
73 | |> should equal [ ("user", "admin") ]
74 |
75 | []
76 | let ``Check EnvironmentVariables with multiple KeyValues config`` () =
77 | cli {
78 | Shell BASH
79 | EnvironmentVariables [ ("user", "admin"); ("path", "path/to/file") ]
80 | }
81 | |> _.config.EnvironmentVariables.Value
82 | |> should equal [ ("user", "admin"); ("path", "path/to/file") ]
83 |
84 | []
85 | let ``Check Encoding with setting Encoding`` () =
86 | cli {
87 | Shell BASH
88 | Encoding System.Text.Encoding.UTF8
89 | }
90 | |> _.config.Encoding.Value
91 | |> should equal System.Text.Encoding.UTF8
92 |
--------------------------------------------------------------------------------
/src/Fli.Tests/paket.references:
--------------------------------------------------------------------------------
1 | FSharp.Core
2 |
3 | FsUnit
4 | Microsoft.NET.Test.Sdk
5 | NUnit
6 | NUnit3TestAdapter
7 | NUnit.Analyzers
--------------------------------------------------------------------------------
/src/Fli.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32901.215
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fli", "Fli\Fli.fsproj", "{401B7916-2350-49CC-96F8-A0280F06B0AE}"
7 | EndProject
8 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fli.Tests", "Fli.Tests\Fli.Tests.fsproj", "{AD2B83A4-C36A-4963-B15C-5C6F8E5A0915}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {401B7916-2350-49CC-96F8-A0280F06B0AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {401B7916-2350-49CC-96F8-A0280F06B0AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {401B7916-2350-49CC-96F8-A0280F06B0AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {401B7916-2350-49CC-96F8-A0280F06B0AE}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {AD2B83A4-C36A-4963-B15C-5C6F8E5A0915}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {AD2B83A4-C36A-4963-B15C-5C6F8E5A0915}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {AD2B83A4-C36A-4963-B15C-5C6F8E5A0915}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {AD2B83A4-C36A-4963-B15C-5C6F8E5A0915}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {C56166F4-FF51-4A92-82DB-AC3A08EA113A}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/src/Fli/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | module AssemblyInfo
2 |
3 | open System.Runtime.CompilerServices
4 |
5 | []
6 | do ()
7 |
--------------------------------------------------------------------------------
/src/Fli/CE.fs:
--------------------------------------------------------------------------------
1 | namespace Fli
2 |
3 | []
4 | module CE =
5 |
6 | open System.Text
7 | open Domain
8 |
9 | type ICommandContext<'a> with
10 |
11 | member this.Yield _ = this
12 |
13 | type StartingContext =
14 | { config: Config option }
15 |
16 | member this.CurrentConfig = this.config |> Option.defaultValue Defaults
17 |
18 | interface ICommandContext with
19 | member this.Context = this
20 |
21 | let cli = { config = None }
22 |
23 | /// Extensions for Shell context.
24 | type ICommandContext<'a> with
25 |
26 | /// `Shell` opening keyword, which `Fli.Shell` shall be used.
27 | []
28 | member _.Shell(context: ICommandContext, shell) =
29 | Cli.shell shell context.Context.CurrentConfig
30 |
31 | /// Which `Command` should be executed in the `Shell`.
32 | []
33 | member _.Command(context: ICommandContext, command) = Cli.command command context.Context
34 |
35 | /// `Input` string(s) that can be used to interact with the shell.
36 | []
37 | member _.Input(context: ICommandContext, input) = Cli.input input context.Context
38 |
39 | /// Extra `Output` that is being executed immediately after getting output from execution.
40 | []
41 | member _.Output(context: ICommandContext, output: Outputs) = Cli.output output context.Context
42 |
43 | /// Extra `Output` that is being executed immediately after getting output from execution.
44 | []
45 | member _.Output(context: ICommandContext, filePath: string) =
46 | Cli.output (File filePath) context.Context
47 |
48 | /// Extra `Output` that is being executed immediately after getting output from execution.
49 | []
50 | member _.Output(context: ICommandContext, stringBuilder: StringBuilder) =
51 | Cli.output (StringBuilder stringBuilder) context.Context
52 |
53 | /// Extra `Output` that is being executed immediately after getting output from execution.
54 | []
55 | member _.Output(context: ICommandContext, func: string -> unit) =
56 | Cli.output (Custom func) context.Context
57 |
58 | /// Current executing `working directory`.
59 | []
60 | member _.WorkingDirectory(context: ICommandContext, workingDirectory) =
61 | Cli.workingDirectory workingDirectory context.Context
62 |
63 | /// The `WindowStyle` for newly created windows.
64 | /// Hint: Hidden, Maximized, Minimized or Normal.
65 | []
66 | member _.WindowStyle(context: ICommandContext, windowStyle) =
67 | Cli.windowStyle windowStyle context.Context
68 |
69 | /// One tupled `EnvironmentVariable`.
70 | []
71 | member _.EnvironmentVariable(context: ICommandContext, environmentVariable) =
72 | Cli.environmentVariables [ environmentVariable ] context.Context
73 |
74 | /// A list of tupled `EnvironmentVariables`.
75 | []
76 | member _.EnvironmentVariables(context: ICommandContext, environmentVariables) =
77 | Cli.environmentVariables environmentVariables context.Context
78 |
79 | /// `Encoding` that'll be used for `Input`, 'Output.Text' and `Output.Error`.
80 | []
81 | member _.Encoding(context: ICommandContext, encoding) = Cli.encoding encoding context.Context
82 |
83 | /// Cancel after a period of time in milliseconds for async operations.
84 | []
85 | member _.CancelAfter(context: ICommandContext, cancelAfter) =
86 | Cli.cancelAfter cancelAfter context.Context
87 |
88 | /// Extensions for Exec context.
89 | type ICommandContext<'a> with
90 |
91 | /// `Exec` opening keyword, which binary/executable shall be started.
92 | []
93 | member _.Exec(context: ICommandContext, program) =
94 | Program.program program context.Context.CurrentConfig
95 |
96 | /// `Arguments` that will be passed into the executable.
97 | []
98 | member _.Arguments(context: ICommandContext, arguments) =
99 | let matchArguments arguments =
100 | match box arguments with
101 | | :? string as s -> s |> Some |> Arguments
102 | | :? seq as s -> s |> Array.ofSeq |> Some |> ArgumentList
103 | | _ -> failwith "Cannot convert arguments to a string!"
104 |
105 | Program.arguments (matchArguments arguments) context.Context
106 |
107 | /// `Input` string(s) that can be used to interact with the executable.
108 | []
109 | member _.Input(context: ICommandContext, input) = Program.input input context.Context
110 |
111 | /// Extra `Output` that is being executed immediately after getting output from execution.
112 | []
113 | member _.Output(context: ICommandContext, output: Outputs) = Program.output output context.Context
114 |
115 | /// Extra `Output` that is being executed immediately after getting output from execution.
116 | []
117 | member _.Output(context: ICommandContext, filePath: string) =
118 | Program.output (File filePath) context.Context
119 |
120 | /// Extra `Output` that is being executed immediately after getting output from execution.
121 | []
122 | member _.Output(context: ICommandContext, stringBuilder: StringBuilder) =
123 | Program.output (StringBuilder stringBuilder) context.Context
124 |
125 | /// Extra `Output` that is being executed immediately after getting output from execution.
126 | []
127 | member _.Output(context: ICommandContext, func: string -> unit) =
128 | Program.output (Custom func) context.Context
129 |
130 | /// Current executing `working directory`.
131 | []
132 | member _.WorkingDirectory(context: ICommandContext, workingDirectory) =
133 | Program.workingDirectory workingDirectory context.Context
134 |
135 | /// The `WindowStyle` for newly created windows.
136 | /// Hint: Hidden, Maximized, Minimized or Normal.
137 | []
138 | member _.WindowStyle(context: ICommandContext, windowStyle) =
139 | Program.windowStyle windowStyle context.Context
140 |
141 | /// `Verb` keyword that can be used to start the executable.
142 | []
143 | member _.Verb(context: ICommandContext, verb) = Program.verb verb context.Context
144 |
145 | /// Start the executable with another `Username`.
146 | []
147 | member _.UserName(context: ICommandContext, userName) =
148 | Program.userName userName context.Context
149 |
150 | /// Start the executable with other `Credentials`.
151 | /// Hint: `Domain` and `Password` are available for Windows systems only.
152 | []
153 | member _.Credentials(context: ICommandContext, credentials) =
154 | let domain, user, pw = credentials in Program.credentials (Credentials(domain, user, pw)) context.Context
155 |
156 | /// One tupled `EnvironmentVariable`.
157 | []
158 | member _.EnvironmentVariable(context: ICommandContext, environmentVariable) =
159 | Program.environmentVariables [ environmentVariable ] context.Context
160 |
161 | /// A list of tupled `EnvironmentVariables`.
162 | []
163 | member _.EnvironmentVariables(context: ICommandContext, environmentVariables) =
164 | Program.environmentVariables environmentVariables context.Context
165 |
166 | /// `Encoding` that'll be used for `Input`, 'Output.Text' and `Output.Error`.
167 | []
168 | member _.Encoding(context: ICommandContext, encoding) =
169 | Program.encoding encoding context.Context
170 |
171 | /// Cancel after a period of time in milliseconds for async operations.
172 | []
173 | member _.CancelAfter(context: ICommandContext, cancelAfter) =
174 | Program.cancelAfter cancelAfter context.Context
175 |
--------------------------------------------------------------------------------
/src/Fli/Command.fs:
--------------------------------------------------------------------------------
1 | namespace Fli
2 |
3 | []
4 | module Command =
5 |
6 | open Domain
7 | open Helpers
8 | open Extensions
9 | open System
10 | open System.IO
11 | open System.Text
12 | open System.Diagnostics
13 | open System.Runtime.InteropServices
14 | open System.Threading
15 |
16 | let private getAvailablePwshExe () =
17 | match RuntimeInformation.IsOSPlatform(OSPlatform.Windows) with
18 | | true -> "pwsh.exe"
19 | | false -> "pwsh"
20 |
21 | let private shellToProcess (shell: Shells) (input: string option) =
22 | match shell with
23 | | CMD -> "cmd.exe", (if input.IsNone then "/c" else "/k")
24 | | PS -> "powershell.exe", "-Command"
25 | | PWSH -> getAvailablePwshExe (), "-Command"
26 | | WSL -> "wsl.exe", "--"
27 | | SH -> "sh", "-c"
28 | | BASH -> "bash", "-c"
29 | | ZSH -> "zsh", "-c"
30 | | CUSTOM(shell, flag) -> shell, flag
31 |
32 | let private getArguments arguments executable =
33 | match arguments with
34 | | Some(Arguments a) ->
35 | let args = (a |> Option.defaultValue "")
36 | ProcessStartInfo(executable, args)
37 | | Some(ArgumentList list) ->
38 | #if NET
39 | ProcessStartInfo(executable, (list |> Option.defaultValue [||]))
40 | #else
41 | ProcessStartInfo(executable, ArgumentList(list).toString())
42 | #endif
43 | | None -> ProcessStartInfo(executable, "")
44 |
45 | let private createProcess executable arguments openDefault =
46 | let processInfo = getArguments arguments executable
47 | processInfo.CreateNoWindow <- true
48 | processInfo.UseShellExecute <- openDefault
49 | processInfo.RedirectStandardInput <- not openDefault
50 | processInfo.RedirectStandardOutput <- not openDefault
51 | processInfo.RedirectStandardError <- not openDefault
52 | processInfo
53 |
54 | let private trim (s: string) = s.TrimEnd([| '\r'; '\n' |])
55 |
56 | let returnOr (sb: StringBuilder) (output: string) =
57 | match (sb.ToString(), output) with
58 | | t, _ when t.Length > 0 -> t
59 | | _, o when o.Length > 0 -> o
60 | | _, _ -> ""
61 |
62 | #if NET
63 | let private startProcessAsync
64 | (inFunc: Process -> Tasks.Task)
65 | (outFunc: string -> unit)
66 | cancellationToken
67 | psi
68 | =
69 | async {
70 | let proc = Process.Start(startInfo = psi)
71 | do! proc |> inFunc |> Async.AwaitTask
72 |
73 | let sbStd = StringBuilder()
74 | let sbErr = StringBuilder()
75 |
76 | proc.OutputDataReceived.AddHandler(
77 | new DataReceivedEventHandler(fun s e ->
78 | use o = proc.StandardOutput
79 | sbStd.Append(o.ReadToEnd()) |> ignore)
80 | )
81 |
82 | proc.ErrorDataReceived.AddHandler(
83 | new DataReceivedEventHandler(fun s e ->
84 | use o = proc.StandardError
85 | sbErr.Append(o.ReadToEnd()) |> ignore)
86 | )
87 |
88 | try
89 | do! proc.WaitForExitAsync(cancellationToken) |> Async.AwaitTask
90 | with :? OperationCanceledException ->
91 | ()
92 |
93 | cancellationToken.ThrowIfCancellationRequested()
94 | let! stdo = proc.StandardOutput.ReadToEndAsync() |> Async.AwaitTask
95 | let text = returnOr sbStd stdo
96 |
97 | let! stde = proc.StandardError.ReadToEndAsync() |> Async.AwaitTask
98 | let error = returnOr sbErr stde
99 |
100 | do text |> outFunc
101 |
102 | return
103 | { Id = proc.Id
104 | Text = text |> trim |> toOption
105 | ExitCode = proc.ExitCode
106 | Error = error |> trim |> toOption }
107 | }
108 | |> Async.StartAsTask
109 | |> Async.AwaitTask
110 | #endif
111 |
112 | let private startProcess (inputFunc: Process -> unit) (outputFunc: string -> unit) psi =
113 | let proc = Process.Start(startInfo = psi)
114 | proc |> inputFunc
115 |
116 | let text =
117 | if psi.UseShellExecute |> not then
118 | proc.StandardOutput.ReadToEnd()
119 | else
120 | ""
121 |
122 | let error =
123 | if psi.UseShellExecute |> not then
124 | proc.StandardError.ReadToEnd()
125 | else
126 | ""
127 |
128 | proc.WaitForExit()
129 |
130 | text |> outputFunc
131 |
132 | { Id = proc.Id
133 | Text = text |> trim |> toOption
134 | ExitCode = proc.ExitCode
135 | Error = error |> trim |> toOption }
136 |
137 |
138 | let private checkVerb (verb: string option) (executable: string) =
139 | match verb with
140 | | Some v ->
141 | let verbs = ProcessStartInfo(executable).Verbs
142 |
143 | if not (verbs |> Array.contains v) then
144 | $"""Unknown verb '{v}'. Possible verbs on '{executable}': {verbs |> String.concat ", "}"""
145 | |> ArgumentException
146 | |> raise
147 | | None -> ()
148 |
149 | let private addEnvironmentVariables (variables: (string * string) list option) (psi: ProcessStartInfo) =
150 | if psi.UseShellExecute |> not then
151 | ((variables |> Option.defaultValue [] |> List.iter psi.Environment.Add), psi)
152 | |> snd
153 | else
154 | psi
155 |
156 | let private addCredentials (credentials: Credentials option) (psi: ProcessStartInfo) =
157 | match credentials with
158 | | Some(Credentials(domain, username, password)) ->
159 | psi.UserName <- username
160 |
161 | if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
162 | psi.Domain <- domain
163 | psi.Password <- (password |> toSecureString)
164 |
165 | psi
166 | | None -> psi
167 |
168 | let private writeInput (input: string option) (encoding: Encoding option) (p: Process) =
169 | match input with
170 | | Some inputText ->
171 | try
172 | use sw = p.StandardInput
173 | sw.WriteLine(inputText, encoding)
174 | sw.Flush()
175 | sw.Close()
176 | with :? IOException as ex when ex.GetType() = typedefof ->
177 | ()
178 | | None -> ()
179 |
180 | let private writeInputAsync (input: string option) (p: Process) =
181 | async {
182 | match input with
183 | | Some inputText ->
184 | try
185 | use sw = p.StandardInput
186 | do! inputText |> sw.WriteLineAsync |> Async.AwaitTask
187 | do! sw.FlushAsync() |> Async.AwaitTask
188 | sw.Close()
189 | with :? IOException as ex when ex.GetType() = typedefof ->
190 | ()
191 | | None -> ()
192 | }
193 | |> Async.StartAsTask
194 |
195 | let private writeOutput (outputType: Outputs option) (output: string) =
196 | match outputType with
197 | | Some(o) ->
198 | match o with
199 | | Outputs.File(file) -> File.WriteAllText(file, output)
200 | | Outputs.StringBuilder(stringBuilder) -> output |> stringBuilder.Append |> ignore
201 | | Outputs.Custom(func) -> func.Invoke(output)
202 | | None -> ()
203 |
204 | let private setupCancellationToken (cancelAfter: int option) =
205 | let cts = new CancellationTokenSource()
206 |
207 | match cancelAfter with
208 | | None -> ()
209 | | Some ca -> cts.CancelAfter(ca)
210 |
211 | cts.Token
212 |
213 | let private quoteBashCommand (context: ShellContext) =
214 | let noQuoteNeeded = [| Shells.CMD; Shells.PWSH; Shells.PS; Shells.WSL |]
215 |
216 | match Array.contains context.config.Shell noQuoteNeeded with
217 | | true -> context.config.Command |> Option.defaultValue ""
218 | | false -> context.config.Command |> Option.defaultValue "" |> (fun s -> $"\"{s}\"")
219 |
220 | let private getProcessWindowStyle (windowStyle: WindowStyle) =
221 | match windowStyle with
222 | | Hidden -> ProcessWindowStyle.Hidden
223 | | Maximized -> ProcessWindowStyle.Maximized
224 | | Minimized -> ProcessWindowStyle.Minimized
225 | | Normal -> ProcessWindowStyle.Normal
226 |
227 | type Command =
228 | static member internal buildProcess(context: ShellContext) =
229 | let proc, flag = (context.config.Shell, context.config.Input) ||> shellToProcess
230 | let command = context |> quoteBashCommand
231 | let args = Arguments(Some $"""{flag} {command}""")
232 |
233 | (createProcess proc (Some args) false)
234 | .With(WorkingDirectory = (context.config.WorkingDirectory |> Option.defaultValue ""))
235 | .With(StandardOutputEncoding = (context.config.Encoding |> Option.defaultValue null))
236 | .With(StandardErrorEncoding = (context.config.Encoding |> Option.defaultValue null))
237 | .With(WindowStyle = getProcessWindowStyle (context.config.WindowStyle |> Option.defaultValue Hidden))
238 | |> addEnvironmentVariables context.config.EnvironmentVariables
239 |
240 | static member internal buildProcess(context: ExecContext) =
241 | checkVerb context.config.Verb context.config.Program
242 |
243 | let openDefault =
244 | context.config.Arguments.IsNone
245 | && context.config.EnvironmentVariables.IsNone
246 | && context.config.Input.IsNone
247 |
248 | (createProcess context.config.Program context.config.Arguments openDefault)
249 | .With(Verb = (context.config.Verb |> Option.defaultValue null))
250 | .With(WorkingDirectory = (context.config.WorkingDirectory |> Option.defaultValue ""))
251 | .With(UserName = (context.config.UserName |> Option.defaultValue ""))
252 | .With(StandardOutputEncoding = (context.config.Encoding |> Option.defaultValue null))
253 | .With(StandardErrorEncoding = (context.config.Encoding |> Option.defaultValue null))
254 | .With(WindowStyle = getProcessWindowStyle (context.config.WindowStyle |> Option.defaultValue Hidden))
255 | |> addCredentials context.config.Credentials
256 | |> addEnvironmentVariables context.config.EnvironmentVariables
257 |
258 | /// Stringifies shell + opening flag and given command.
259 | static member toString(context: ShellContext) =
260 | let proc, flag = (context.config.Shell, context.config.Input) ||> shellToProcess
261 | let command = context |> quoteBashCommand
262 | $"""{proc} {flag} {command}"""
263 |
264 | /// Stringifies executable + arguments.
265 | static member toString(context: ExecContext) =
266 | let args =
267 | if context.config.Arguments.IsSome then
268 | context.config.Arguments.Value.toString ()
269 | else
270 | ""
271 |
272 | $"""{context.config.Program} {args}"""
273 |
274 | /// Executes the given context as a new process.
275 | static member execute(context: ShellContext) =
276 | context
277 | |> Command.buildProcess
278 | |> startProcess
279 | (writeInput context.config.Input context.config.Encoding)
280 | (writeOutput context.config.Output)
281 |
282 | /// Executes the given context as a new process.
283 | static member execute(context: ExecContext) =
284 | context
285 | |> Command.buildProcess
286 | |> startProcess
287 | (writeInput context.config.Input context.config.Encoding)
288 | (writeOutput context.config.Output)
289 |
290 | #if NET
291 | /// Executes the given context as a new process asynchronously.
292 | static member executeAsync(context: ShellContext) =
293 | context
294 | |> Command.buildProcess
295 | |> startProcessAsync
296 | (writeInputAsync context.config.Input)
297 | (writeOutput context.config.Output)
298 | (setupCancellationToken context.config.CancelAfter)
299 |
300 | /// Executes the given context as a new process asynchronously.
301 | static member executeAsync(context: ExecContext) =
302 | context
303 | |> Command.buildProcess
304 | |> startProcessAsync
305 | (writeInputAsync context.config.Input)
306 | (writeOutput context.config.Output)
307 | (setupCancellationToken context.config.CancelAfter)
308 | #endif
309 |
--------------------------------------------------------------------------------
/src/Fli/Domain.fs:
--------------------------------------------------------------------------------
1 | namespace Fli
2 |
3 | [