Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

PipeBuilder + refactoring and tests #5

Merged
merged 8 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
.idea/**/usage.statistics.xml
.idea/**/shelf
.idea/**/contentModel.xml
.idea/**/codeStyleConfig.xml
.idea/**/discord.xml

# Artifacts
bin/
obj/
*.nupkg

# User Specific
*.user
1 change: 1 addition & 0 deletions CliWrap.FSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{BC5059
.editorconfig = .editorconfig
.gitignore = .gitignore
.githooks\pre-commit = .githooks\pre-commit
CliWrap.FSharp.sln.DotSettings = CliWrap.FSharp.sln.DotSettings
EndProjectSection
EndProject
Global
Expand Down
14 changes: 13 additions & 1 deletion CliWrap.FSharp.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=execf/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=argf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=argse/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=argsf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=commandv/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=creds/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=credsf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=devnull/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=envf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=execf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ftce/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=streamf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=streamft/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=stringe/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
4 changes: 0 additions & 4 deletions CliWrap.FSharp.sln.DotSettings.user

This file was deleted.

16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ It attempts to mimic the builder pattern and `.With*` style methods.

```fsharp
let main args =
let built = command "dotnet" {
let cmd = command "dotnet" {
args = [ "build" ]
workingDirectory = "~/src/CliWrap.FSharp"
}

built.ExecuteAsync()
cmd.ExecuteAsync()
```

```fsharp
Expand All @@ -23,11 +23,21 @@ let main args = async {
args = [ "build" ]
workingDirectory = "~/src/CliWrap.FSharp"
}

result.ExitCode
}
```

```fsharp
let main args =
let cmd = pipeline {
"an inline string source"
Cli.wrap "echo"
}

cmd.ExecuteAsync()
```

## Idiomatic? This looks nothing like normal F# code!

I've only recently been diving further into the F# ecosystem, if something looks off please open an issue!
Expand Down
9 changes: 5 additions & 4 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"sdk": {
"version": "8.0.101"
}
}
"sdk": {
"version": "8.0.100",
"rollForward": "latestMinor"
}
}
13 changes: 13 additions & 0 deletions src/CliWrap.FSharp.Tests/CliTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module CliTests

open CliWrap
open FsCheck.Xunit
open UnMango.CliWrap.FSharp

[<Property>]
let ``Should create command`` target =
let expected = Command(target)

let actual = Cli.wrap target

expected.TargetFilePath = actual.TargetFilePath
1 change: 1 addition & 0 deletions src/CliWrap.FSharp.Tests/CliWrap.FSharp.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="CliTests.fs" />
<Compile Include="CommandBuilderTests.fs"/>
<Compile Include="Program.fs"/>
<None Include="packages.lock.json" />
Expand Down
41 changes: 41 additions & 0 deletions src/CliWrap.FSharp.Tests/CommandBuilderTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module CommandBuilderTests
open System.Collections.Generic
open System.IO
open System.Linq
open System.Text
open CliWrap
open FsCheck
open FsCheck.Xunit
Expand Down Expand Up @@ -49,3 +50,43 @@ let ``Should configure stdin`` (input: NonNull<string>) =
result.StandardInputPipe.CopyToAsync(b).Wait()

a.ToArray() = b.ToArray()


[<Property>]
let ``Should configure stdout`` () =
let a, b = StringBuilder(), StringBuilder()

let expected =
Command("echo")
.WithArguments([ "testing" ])
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(a))

let result = command "echo" {
args [ "testing" ]
stdout (PipeTarget.ToStringBuilder(b))
}

expected.ExecuteAsync().Task.Wait()
result.ExecuteAsync().Task.Wait()
a.ToString() = b.ToString()


[<Property>]
let ``Should configure stderr`` () =
let a, b = StringBuilder(), StringBuilder()

let expected =
Command("echo")
.WithArguments([ "testing" ])
.WithValidation(CommandResultValidation.None)
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(a))

let result = command "echo" {
args [ "testing" ]
stderr (PipeTarget.ToStringBuilder(b))
}

expected.ExecuteAsync().Task.Wait()
result.WithValidation(CommandResultValidation.None).ExecuteAsync().Task.Wait()

a.ToString() = b.ToString()
8 changes: 4 additions & 4 deletions src/CliWrap.FSharp.Tests/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
},
"FSharp.Core": {
"type": "Direct",
"requested": "[8.0.101, )",
"resolved": "8.0.101",
"contentHash": "sOLz3O4BOxnTKfd5OChdRmDUy4Id0GfoEClRG4nzIod8LY1LJZcNyygKAV0A78XOLh8yvhA5hsDYKZXGCR9blw=="
"requested": "[8.0.100, )",
"resolved": "8.0.100",
"contentHash": "ZOVZ/o+jI3ormTZOa28Wh0tSRoyle1f7lKFcUN61sPiXI7eDZu8eSveFybgTeyIEyW0ujjp31cp7GOglDgsNEg=="
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
Expand Down Expand Up @@ -1047,7 +1047,7 @@
"type": "Project",
"dependencies": {
"CliWrap": "[3.6.4, )",
"FSharp.Core": "[8.0.101, )"
"FSharp.Core": "[8.0.100, )"
}
}
}
Expand Down
177 changes: 174 additions & 3 deletions src/CliWrap.FSharp/Cli.fs
Original file line number Diff line number Diff line change
@@ -1,12 +1,183 @@
module UnMango.CliWrap.FSharp.Cli

open System
open System.Collections.Generic
open CliWrap
open CliWrap.Builders

/// <summary>
/// Creates a copy of this command, setting the arguments to the specified value.
/// </summary>
/// <param name="args">The string representation of the arguments.</param>
/// <param name="command">The Command object to add the arguments to.</param>
/// <returns>A new Command object with the specified arguments.</returns>
/// <remarks>
/// Avoid using this overload, as it requires the arguments to be escaped manually.
/// Formatting errors may lead to unexpected bugs and security vulnerabilities.
/// </remarks>
let arg (args: string) (command: Command) = command.WithArguments(args)

Check warning on line 18 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L18

Added line #L18 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the arguments to the value obtained by formatting the specified enumeration.
/// </summary>
/// <param name="args">The arguments to be added to the command.</param>
/// <param name="command">The command to add the arguments to.</param>
/// <returns>A new command object with the provided arguments.</returns>
let args (args: string seq) (command: Command) = command.WithArguments(args)

Check warning on line 26 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L26

Added line #L26 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the arguments to the value obtained by formatting the specified enumeration.
/// </summary>
/// <param name="args">The arguments to be added to the command.</param>
/// <param name="escape">The escape options to apply to the arguments.</param>
/// <param name="command">The original command.</param>
/// <returns>A new command object with the arguments and escape options added.</returns>
let argse args escape (command: Command) = command.WithArguments(args, escape)

Check warning on line 35 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L35

Added line #L35 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the arguments to the value configured by the specified delegate.
/// </summary>
/// <param name="f">The function that builds the arguments using an ArgumentsBuilder.</param>
/// <param name="command">The command for which the arguments are being built.</param>
/// <returns>A new command object with the arguments added.</returns>
let argsf (f: ArgumentsBuilder -> unit) (command: Command) = command.WithArguments(f)

Check warning on line 43 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L43

Added line #L43 was not covered by tests

/// <summary>
/// Creates a new command with the specified parameters.
/// </summary>
/// <param name="target">The target executable or script file.</param>
/// <param name="args">The arguments to be passed to the target.</param>
/// <param name="workDir">The working directory for the command.</param>
/// <param name="creds">The credentials to use for the command.</param>
/// <param name="env">The environment variables for the command.</param>
/// <param name="v">The validation for the command.</param>
/// <param name="stdin">The input pipe source for the command.</param>
/// <param name="stdout">The output pipe target for the command.</param>
/// <param name="stderr">The error pipe target for the command.</param>
/// <returns>A new Command instance.</returns>
/// <remarks>
/// v = verbose. Idk why you would use this, but its there if you want to
/// </remarks>
let commandv target args workDir creds env v stdin stdout stderr =
Command(target, args, workDir, creds, env, v, stdin, stdout, stderr)

Check warning on line 62 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L62

Added line #L62 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the user credentials to the specified value.
/// </summary>
/// <param name="credentials">The credentials to be applied.</param>
/// <param name="command">The command to apply the credentials to.</param>
/// <returns>The modified command with the credentials applied.</returns>
let creds (credentials: Credentials) (command: Command) = command.WithCredentials(credentials)

Check warning on line 70 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L70

Added line #L70 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the user credentials to the specified value.
/// </summary>
/// <param name="f">The function that builds the credentials with a CredentialsBuilder.</param>
/// <param name="command">The command for which the credentials are being built.</param>
/// <returns>The modified command with the credentials applied.</returns>
let credsf (f: CredentialsBuilder -> unit) (command: Command) = command.WithCredentials(f)

Check warning on line 78 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L78

Added line #L78 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the environment variables to the specified value.
/// </summary>
/// <param name="env">A sequence of tuples representing key-value pairs of environment variables.</param>
/// <param name="command">The command to modify.</param>
/// <returns>A new command with the updated environment variables.</returns>
let env (env: (string * string) seq) (command: Command) =
command.WithEnvironmentVariables((dict env).AsReadOnly())

Check warning on line 87 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L87

Added line #L87 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the environment variables to the value configured by the specified delegate.
/// </summary>
/// <param name="f">The function that builds environment variables using an EnvironmentVariablesBuilder.</param>
/// <param name="command">The command to which the environment variables should be applied.</param>
/// <returns>The modified command with the updated environment variables.</returns>
let envf (f: EnvironmentVariablesBuilder -> unit) (command: Command) = command.WithEnvironmentVariables(f)

Check warning on line 95 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L95

Added line #L95 was not covered by tests

/// <summary>
/// Executes the command asynchronously.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <returns>An <see cref="Async{CommandResult}"/> representing the asynchronous operation.</returns>
let exec (command: Command) =
command.ExecuteAsync() |> CommandTask.op_Implicit |> Async.AwaitTask

module C =
let exec (command: Command) cancellationToken = command.ExecuteAsync(cancellationToken)
/// <summary>
/// Creates a copy of this command, setting the standard input pipe to the specified source.
/// </summary>
/// <param name="pipe">The pipe to attach to the standard input.</param>
/// <param name="command">The command to attach the pipe to.</param>
/// <returns>A copy of the command with the standard input pipe attached.</returns>
let stdin pipe (command: Command) = command.WithStandardInputPipe(pipe)

Check warning on line 111 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L111

Added line #L111 was not covered by tests

let execf (command: Command) forceful graceful =
/// <summary>
/// Creates a copy of this command, setting the standard output pipe to the specified target.
/// </summary>
/// <param name="pipe">The pipe to redirect the standard output to.</param>
/// <param name="command">The command to redirect the standard output from.</param>
/// <returns>A copy of the command with the standard output pipe attached.</returns>
let stdout pipe (command: Command) = command.WithStandardOutputPipe(pipe)

Check warning on line 119 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L119

Added line #L119 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the standard error pipe to the specified target.
/// </summary>
/// <param name="pipe">The pipe to attach.</param>
/// <param name="command">The command to attach the pipe to.</param>
/// <returns>The modified command with the standard error pipe attached.</returns>
let stderr pipe (command: Command) = command.WithStandardErrorPipe(pipe)

Check warning on line 127 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L127

Added line #L127 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the target file path to the specified value.
/// </summary>
/// <param name="target">The target file to set.</param>
/// <param name="command">The original command instance.</param>
/// <returns>A new Command instance with the updated target file.</returns>
let target target (command: Command) = command.WithTargetFile(target)

Check warning on line 135 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L135

Added line #L135 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the validation options to the specified value.
/// </summary>
/// <param name="validation">The validation options to apply.</param>
/// <param name="command">The command to validate.</param>
/// <returns>A copy of the command with the validation options applied.</returns>
let validation validation (command: Command) = command.WithValidation(validation)

Check warning on line 143 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L143

Added line #L143 was not covered by tests

/// <summary>
/// Creates a copy of this command, setting the working directory path to the specified value.
/// </summary>
/// <param name="dir">The directory to set as the working directory.</param>
/// <param name="command">The command to modify.</param>
/// <returns>
/// A new command object with the working directory set to the specified directory.
/// </returns>
let workDir dir (command: Command) = command.WithWorkingDirectory(dir)

Check warning on line 153 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L153

Added line #L153 was not covered by tests

/// <summary>
/// Creates a new command to execute the target specified by <paramref name="target"/>.
/// </summary>
/// <param name="target">The target to be wrapped.</param>
/// <returns>A command to execute <paramref name="target"/>.</returns>
let wrap target = Command(target)

Check warning on line 160 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L160

Added line #L160 was not covered by tests

module Task =
/// <summary>
/// Executes the command asynchronously.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="command">The command to execute.</param>
/// <returns>A task representing the execution of the command.</returns>
/// <remarks><see cref="CommandTask"/> can be <see cref="await"/>ed like a <see cref="Task"/>.</remarks>
let exec cancellationToken (command: Command) = command.ExecuteAsync(cancellationToken)

Check warning on line 170 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L170

Added line #L170 was not covered by tests

/// <summary>
/// Executes the command asynchronously.
/// </summary>
/// <param name="forceful">The cancellation token to forcefully exit.</param>
/// <param name="graceful">The cancellation token to gracefully exit.</param>
/// <param name="command">The command to execute.</param>
/// <returns>A task representing the execution of the command.</returns>
/// <remarks><see cref="CommandTask"/> can be <see cref="await"/>ed like a <see cref="Task"/>.</remarks>
let execf forceful graceful (command: Command) =
command.ExecuteAsync(graceful, forceful)

let toString (command: Command) = command.ToString()

Check warning on line 183 in src/CliWrap.FSharp/Cli.fs

View check run for this annotation

Codecov / codecov/patch

src/CliWrap.FSharp/Cli.fs#L183

Added line #L183 was not covered by tests
5 changes: 3 additions & 2 deletions src/CliWrap.FSharp/CliWrap.FSharp.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="Types.fs" />
<Compile Include="Cli.fs" />
<Compile Include="Pipes.fs" />
<Compile Include="PipeBuilder.fs" />
<Compile Include="CommandBuilder.fs" />
<Compile Include="ExecBuilder.fs" />
<Compile Include="Cli.fs" />
<None Include="packages.lock.json" />
</ItemGroup>

Expand Down
Loading