Skip to content

Commit

Permalink
Update docs for isStdin/isStdout
Browse files Browse the repository at this point in the history
  • Loading branch information
ajalt committed Feb 9, 2021
1 parent 07b8c64 commit 979fcdf
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 80 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Added
- `InputStream.isCliktParameterDefaultStdin` and `OutputStream.isCliktParameterDefaultStdout` to check if the streams returned from `inputStream`/`outputStream` options are proxying stdin/stdout ([#272](https://github.com/ajalt/clikt/issues/272))

### Changed
- Make parameters of `mutuallyExclusiveOptions` covariant to allow validation without explicit type annotations. ([#265](https://github.com/ajalt/clikt/issues/265))
- Update kotlin to 1.4.30
Expand Down
Expand Up @@ -10,40 +10,20 @@ import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.Files

private fun convertToInputStream(s: String, fileSystem: FileSystem, context: Context, fail: (String) -> Unit): InputStream {
return if (s == "-") {
UnclosableInputStream(System.`in`)
} else {
val path = convertToPath(
path = s,
mustExist = true,
canBeFile = true,
canBeFolder = false,
mustBeWritable = false,
mustBeReadable = true,
canBeSymlink = true,
fileSystem = fileSystem,
context = context,
fail = fail
)
Files.newInputStream(path)
}
}

//<editor-fold desc="options">
// region ========== Options ==========

/**
* Convert the option to an [InputStream].
*
* The value given on the command line must be either a path to a readable file, or `-`. If `-` is
* given, stdin will be used.
*
* If stdin is used, the resulting [InputStream] will be a proxy for [System.in] that will not close
* If stdin is used, the resulting [InputStream] will be a proxy for [System. in] that will not close
* the underlying stream. So you can always [close][InputStream.close] the resulting stream without
* worrying about accidentally closing [System.in].
* worrying about accidentally closing [System. in].
*/
fun RawOption.inputStream(
fileSystem: FileSystem = FileSystems.getDefault()
fileSystem: FileSystem = FileSystems.getDefault(),
): NullableOption<InputStream, InputStream> {
return convert({ localization.fileMetavar() }, CompletionCandidates.Path) { s ->
convertToInputStream(s, fileSystem, context) { fail(it) }
Expand All @@ -57,21 +37,21 @@ fun NullableOption<InputStream, InputStream>.defaultStdin(): OptionWithValues<In
return default(UnclosableInputStream(System.`in`), "-")
}

//</editor-fold>
//<editor-fold desc="arguments">
// endregion
// region ========== Arguments ==========

/**
* Convert the argument to an [InputStream].
*
* The value given on the command line must be either a path to a readable file, or `-`. If `-` is
* given, stdin will be used.
*
* If stdin is used, the resulting [InputStream] will be a proxy for [System.in] that will not close
* If stdin is used, the resulting [InputStream] will be a proxy for [System. in] that will not close
* the underlying stream. So you can always [close][InputStream.close] the resulting stream without
* worrying about accidentally closing [System.in].
* worrying about accidentally closing [System. in].
*/
fun RawArgument.inputStream(
fileSystem: FileSystem = FileSystems.getDefault()
fileSystem: FileSystem = FileSystems.getDefault(),
): ProcessedArgument<InputStream, InputStream> {
return convert(completionCandidates = CompletionCandidates.Path) { s ->
convertToInputStream(s, fileSystem, context) { fail(it) }
Expand All @@ -85,12 +65,40 @@ fun ProcessedArgument<InputStream, InputStream>.defaultStdin(): ArgumentDelegate
return default(UnclosableInputStream(System.`in`))
}

//</editor-fold>
// endregion

/**
* Checks whether this stream is an unclosable [System.`in`] proxy.
* Checks whether this stream was returned from an [inputStream] parameter, and that it is
* reading from [System. in] (because `-` was given, or no value was given and the parameter uses
* [defaultStdin]).
*/
fun InputStream.isCliktParameterDefaultStdin(): Boolean = this is UnclosableInputStream
val InputStream.isCliktParameterDefaultStdin: Boolean
get() = this is UnclosableInputStream

private fun convertToInputStream(
s: String,
fileSystem: FileSystem,
context: Context,
fail: (String) -> Unit,
): InputStream {
return if (s == "-") {
UnclosableInputStream(System.`in`)
} else {
val path = convertToPath(
path = s,
mustExist = true,
canBeFile = true,
canBeFolder = false,
mustBeWritable = false,
mustBeReadable = true,
canBeSymlink = true,
fileSystem = fileSystem,
context = context,
fail = fail
)
Files.newInputStream(path)
}
}

private class UnclosableInputStream(private var delegate: InputStream?) : InputStream() {
private val stream get() = delegate ?: throw IOException("Stream closed")
Expand All @@ -108,3 +116,5 @@ private class UnclosableInputStream(private var delegate: InputStream?) : InputS
delegate = null
}
}

// endregion
Expand Up @@ -11,35 +11,7 @@ import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.StandardOpenOption.*

private fun convertToOutputStream(
s: String,
createIfNotExist: Boolean,
truncateExisting: Boolean,
fileSystem: FileSystem,
context: Context,
fail: (String) -> Unit
): OutputStream {
return if (s == "-") {
UnclosableOutputStream(System.out)
} else {
val path = convertToPath(
s,
mustExist = !createIfNotExist,
canBeFile = true,
canBeFolder = false,
mustBeWritable = !createIfNotExist,
mustBeReadable = false,
canBeSymlink = true,
fileSystem = fileSystem,
context = context
) { fail(it) }
val openType = if (truncateExisting) TRUNCATE_EXISTING else APPEND
val options = arrayOf(WRITE, CREATE, openType)
Files.newOutputStream(path, *options)
}
}

//<editor-fold desc="options">
// region ========== Options ==========

/**
* Convert the option to an [OutputStream].
Expand All @@ -55,9 +27,9 @@ private fun convertToOutputStream(
* @param truncateExisting If true, existing files will be truncated when opened. By default, the file will be appended to.
*/
fun RawOption.outputStream(
createIfNotExist: Boolean = true,
truncateExisting: Boolean = false,
fileSystem: FileSystem = FileSystems.getDefault()
createIfNotExist: Boolean = true,
truncateExisting: Boolean = false,
fileSystem: FileSystem = FileSystems.getDefault(),
): NullableOption<OutputStream, OutputStream> {
return convert({ localization.fileMetavar() }, CompletionCandidates.Path) { s ->
convertToOutputStream(s, createIfNotExist, truncateExisting, fileSystem, context) { fail(it) }
Expand All @@ -71,8 +43,8 @@ fun NullableOption<OutputStream, OutputStream>.defaultStdout(): OptionWithValues
return default(UnclosableOutputStream(System.out), "-")
}

//</editor-fold>
//<editor-fold desc="arguments">
// endregion
// region ========== Arguments ==========

/**
* Convert the argument to an [OutputStream].
Expand All @@ -88,9 +60,9 @@ fun NullableOption<OutputStream, OutputStream>.defaultStdout(): OptionWithValues
* @param truncateExisting If true, existing files will be truncated when opened. By default, the file will be appended to.
*/
fun RawArgument.outputStream(
createIfNotExist: Boolean = true,
truncateExisting: Boolean = false,
fileSystem: FileSystem = FileSystems.getDefault()
createIfNotExist: Boolean = true,
truncateExisting: Boolean = false,
fileSystem: FileSystem = FileSystems.getDefault(),
): ProcessedArgument<OutputStream, OutputStream> {
return convert(completionCandidates = CompletionCandidates.Path) { s ->
convertToOutputStream(s, createIfNotExist, truncateExisting, fileSystem, context) { fail(it) }
Expand All @@ -104,12 +76,43 @@ fun ProcessedArgument<OutputStream, OutputStream>.defaultStdout(): ArgumentDeleg
return default(UnclosableOutputStream(System.out))
}

//</editor-fold>
// endregion

/**
* Checks whether this stream is an unclosable [System.out] proxy.
* Checks whether this stream was returned from an [outputStream] parameter, and that it is
* writing to [System.out] (because `-` was given, or no value was given and the parameter uses
* [defaultStdout]).
*/
fun OutputStream.isCliktParameterDefaultStdout(): Boolean = this is UnclosableOutputStream
val OutputStream.isCliktParameterDefaultStdout: Boolean
get() = this is UnclosableOutputStream

private fun convertToOutputStream(
s: String,
createIfNotExist: Boolean,
truncateExisting: Boolean,
fileSystem: FileSystem,
context: Context,
fail: (String) -> Unit,
): OutputStream {
return if (s == "-") {
UnclosableOutputStream(System.out)
} else {
val path = convertToPath(
s,
mustExist = !createIfNotExist,
canBeFile = true,
canBeFolder = false,
mustBeWritable = !createIfNotExist,
mustBeReadable = false,
canBeSymlink = true,
fileSystem = fileSystem,
context = context
) { fail(it) }
val openType = if (truncateExisting) TRUNCATE_EXISTING else APPEND
val options = arrayOf(WRITE, CREATE, openType)
Files.newOutputStream(path, *options)
}
}

private class UnclosableOutputStream(private var delegate: OutputStream?) : OutputStream() {
private val stream get() = delegate ?: throw IOException("Stream closed")
Expand Down
Expand Up @@ -78,7 +78,7 @@ class InputStreamTest {
val option by option().inputStream(fs).defaultStdin()

override fun run_() {
option.isCliktParameterDefaultStdin().shouldBeTrue()
option.isCliktParameterDefaultStdin.shouldBeTrue()
}
}

Expand All @@ -93,7 +93,7 @@ class InputStreamTest {
val option by option().inputStream(fs)

override fun run_() {
option?.isCliktParameterDefaultStdin()?.shouldBeFalse()
option?.isCliktParameterDefaultStdin?.shouldBeFalse()
}
}

Expand All @@ -106,7 +106,7 @@ class InputStreamTest {
val stream by argument().inputStream(fs).defaultStdin()

override fun run_() {
stream.isCliktParameterDefaultStdin().shouldBeTrue()
stream.isCliktParameterDefaultStdin.shouldBeTrue()
}
}

Expand All @@ -121,7 +121,7 @@ class InputStreamTest {
val stream by argument().inputStream(fs)

override fun run_() {
stream.isCliktParameterDefaultStdin().shouldBeFalse()
stream.isCliktParameterDefaultStdin.shouldBeFalse()
}
}

Expand Down
Expand Up @@ -84,7 +84,7 @@ class OutputStreamTest {
val option by option().outputStream(fileSystem = fs).defaultStdout()

override fun run_() {
option.isCliktParameterDefaultStdout().shouldBeTrue()
option.isCliktParameterDefaultStdout.shouldBeTrue()
}
}

Expand All @@ -97,7 +97,7 @@ class OutputStreamTest {
val option by option().outputStream(fileSystem = fs)

override fun run_() {
option?.isCliktParameterDefaultStdout()?.shouldBeFalse()
option?.isCliktParameterDefaultStdout?.shouldBeFalse()
}
}

Expand All @@ -110,7 +110,7 @@ class OutputStreamTest {
val stream by argument().outputStream(fileSystem = fs).defaultStdout()

override fun run_() {
stream.isCliktParameterDefaultStdout().shouldBeTrue()
stream.isCliktParameterDefaultStdout.shouldBeTrue()
}
}

Expand All @@ -123,7 +123,7 @@ class OutputStreamTest {
val stream by argument().outputStream(fileSystem = fs)

override fun run_() {
stream.isCliktParameterDefaultStdout().shouldBeFalse()
stream.isCliktParameterDefaultStdout.shouldBeFalse()
}
}

Expand Down
5 changes: 5 additions & 0 deletions docs/parameters.md
Expand Up @@ -145,6 +145,9 @@ reading or writing. They support the unix convention of passing `-` to specify s
rather than a file on the filesystem. You'll need to close the streams yourself. You can also use
[stdin][defaultStdin] or [stdout][defaultStdout] as their default values.

If you need to check if one of these streams is pointing to a file rather than stdin or stdout, you
can use [`isCliktParameterDefaultStdin`][isStdin] or [`isCliktParameterDefaultStdout`][isStdout].

## Custom Types

You can convert parameter values to a custom type by using
Expand Down Expand Up @@ -338,6 +341,8 @@ been set, so (unlike in transforms) you can reference other parameters:
[float]: api/clikt/com.github.ajalt.clikt.parameters.types/float.md
[inputStream]: api/clikt/com.github.ajalt.clikt.parameters.types/input-stream.md
[int]: api/clikt/com.github.ajalt.clikt.parameters.types/int.md
[isStdin]: api/clikt/com.github.ajalt.clikt.parameters.types/java.io.-input-stream/is-clikt-parameter-default-stdin.md
[isStdout]: api/clikt/com.github.ajalt.clikt.parameters.types/java.io.-output-stream/is-clikt-parameter-default-stdout.md
[long]: api/clikt/com.github.ajalt.clikt.parameters.types/long.md
[outputStream]: api/clikt/com.github.ajalt.clikt.parameters.types/output-stream.md
[path]: api/clikt/com.github.ajalt.clikt.parameters.types/path.md
Expand Down

0 comments on commit 979fcdf

Please sign in to comment.