@effect/platform 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,211 @@
1
+ import * as Chunk from "@effect/data/Chunk"
2
+ import { dual } from "@effect/data/Function"
3
+ import * as HashMap from "@effect/data/HashMap"
4
+ import * as Option from "@effect/data/Option"
5
+ import type ReadonlyArray from "@effect/data/ReadonlyArray"
6
+ import * as Effect from "@effect/io/Effect"
7
+ import type * as Command from "@effect/platform/Command"
8
+ import type * as CommandExecutor from "@effect/platform/CommandExecutor"
9
+ import type { PlatformError } from "@effect/platform/Error"
10
+ import * as commandExecutor from "@effect/platform/internal/commandExecutor"
11
+ import * as Stream from "@effect/stream/Stream"
12
+
13
+ /** @internal */
14
+ export const CommandTypeId: Command.CommandTypeId = Symbol.for("@effect/platform/Command") as Command.CommandTypeId
15
+
16
+ /** @internal */
17
+ export const isCommand = (u: unknown): u is Command.Command => typeof u === "object" && u != null && CommandTypeId in u
18
+
19
+ /** @internal */
20
+ export const env: {
21
+ (environment: Record<string, string>): (self: Command.Command) => Command.Command
22
+ (self: Command.Command, environment: Record<string, string>): Command.Command
23
+ } = dual<
24
+ (environment: Record<string, string>) => (self: Command.Command) => Command.Command,
25
+ (self: Command.Command, environment: Record<string, string>) => Command.Command
26
+ >(2, (self, environment) => {
27
+ switch (self._tag) {
28
+ case "StandardCommand": {
29
+ return { ...self, env: HashMap.union(self.env, HashMap.fromIterable(Object.entries(environment))) }
30
+ }
31
+ case "PipedCommand": {
32
+ return pipeTo(env(self.left, environment), env(self.right, environment))
33
+ }
34
+ }
35
+ })
36
+
37
+ /** @internal */
38
+ export const exitCode = (
39
+ self: Command.Command
40
+ ): Effect.Effect<CommandExecutor.CommandExecutor, PlatformError, CommandExecutor.ExitCode> =>
41
+ Effect.flatMap(commandExecutor.CommandExecutor, (executor) => executor.exitCode(self))
42
+
43
+ /** @internal */
44
+ export const feed = dual<
45
+ (input: string) => (self: Command.Command) => Command.Command,
46
+ (self: Command.Command, input: string) => Command.Command
47
+ >(2, (self, input) => stdin(self, Stream.fromChunk(Chunk.of(new TextEncoder().encode(input)))))
48
+
49
+ /** @internal */
50
+ export const flatten = (self: Command.Command): ReadonlyArray.NonEmptyReadonlyArray<Command.StandardCommand> =>
51
+ Array.from(flattenLoop(self)) as unknown as ReadonlyArray.NonEmptyReadonlyArray<
52
+ Command.StandardCommand
53
+ >
54
+
55
+ /** @internal */
56
+ const flattenLoop = (self: Command.Command): Chunk.NonEmptyChunk<Command.StandardCommand> => {
57
+ switch (self._tag) {
58
+ case "StandardCommand": {
59
+ return Chunk.of(self)
60
+ }
61
+ case "PipedCommand": {
62
+ return Chunk.appendAll(
63
+ flattenLoop(self.left),
64
+ flattenLoop(self.right)
65
+ ) as Chunk.NonEmptyChunk<Command.StandardCommand>
66
+ }
67
+ }
68
+ }
69
+
70
+ /** @internal */
71
+ export const lines = (
72
+ command: Command.Command,
73
+ encoding = "utf-8"
74
+ ): Effect.Effect<CommandExecutor.CommandExecutor, PlatformError, ReadonlyArray<string>> =>
75
+ Effect.flatMap(commandExecutor.CommandExecutor, (executor) => executor.lines(command, encoding))
76
+
77
+ /** @internal */
78
+ export const make = (command: string, ...args: Array<string>): Command.Command => ({
79
+ [CommandTypeId]: CommandTypeId,
80
+ _tag: "StandardCommand",
81
+ command,
82
+ args,
83
+ env: HashMap.empty(),
84
+ cwd: Option.none(),
85
+ // The initial process input here does not matter, we just want the child
86
+ // process to default to `"pipe"` for the stdin stream.
87
+ stdin: Option.some(Stream.empty),
88
+ stdout: "pipe",
89
+ stderr: "pipe",
90
+ gid: Option.none(),
91
+ uid: Option.none()
92
+ })
93
+
94
+ /** @internal */
95
+ export const pipeTo = dual<
96
+ (into: Command.Command) => (self: Command.Command) => Command.Command,
97
+ (self: Command.Command, into: Command.Command) => Command.Command
98
+ >(2, (self, into) => ({
99
+ [CommandTypeId]: CommandTypeId,
100
+ _tag: "PipedCommand",
101
+ left: self,
102
+ right: into
103
+ }))
104
+
105
+ /** @internal */
106
+ export const stderr: {
107
+ (stderr: Command.Command.Output): (self: Command.Command) => Command.Command
108
+ (self: Command.Command, stderr: Command.Command.Output): Command.Command
109
+ } = dual<
110
+ (stderr: Command.Command.Output) => (self: Command.Command) => Command.Command,
111
+ (self: Command.Command, stderr: Command.Command.Output) => Command.Command
112
+ >(2, (self, output) => {
113
+ switch (self._tag) {
114
+ case "StandardCommand": {
115
+ return { ...self, stderr: output }
116
+ }
117
+ // For piped commands it only makes sense to provide `stderr` for the
118
+ // right-most command as the rest will be piped in.
119
+ case "PipedCommand": {
120
+ return { ...self, right: stderr(self.right, output) }
121
+ }
122
+ }
123
+ })
124
+
125
+ /** @internal */
126
+ export const stdin: {
127
+ (stdin: Command.Command.Input): (self: Command.Command) => Command.Command
128
+ (self: Command.Command, stdin: Command.Command.Input): Command.Command
129
+ } = dual<
130
+ (stdin: Command.Command.Input) => (self: Command.Command) => Command.Command,
131
+ (self: Command.Command, stdin: Command.Command.Input) => Command.Command
132
+ >(2, (self, input) => {
133
+ switch (self._tag) {
134
+ case "StandardCommand": {
135
+ return { ...self, stdin: Option.some(input) }
136
+ }
137
+ // For piped commands it only makes sense to provide `stdin` for the
138
+ // left-most command as the rest will be piped in.
139
+ case "PipedCommand": {
140
+ return { ...self, left: stdin(self.left, input) }
141
+ }
142
+ }
143
+ })
144
+
145
+ /** @internal */
146
+ export const stdout: {
147
+ (stdout: Command.Command.Output): (self: Command.Command) => Command.Command
148
+ (self: Command.Command, stdout: Command.Command.Output): Command.Command
149
+ } = dual<
150
+ (stdout: Command.Command.Output) => (self: Command.Command) => Command.Command,
151
+ (self: Command.Command, stdout: Command.Command.Output) => Command.Command
152
+ >(2, (self, output) => {
153
+ switch (self._tag) {
154
+ case "StandardCommand": {
155
+ return { ...self, stdout: output }
156
+ }
157
+ // For piped commands it only makes sense to provide `stderr` for the
158
+ // right-most command as the rest will be piped in.
159
+ case "PipedCommand": {
160
+ return { ...self, right: stdout(self.right, output) }
161
+ }
162
+ }
163
+ })
164
+
165
+ /** @internal */
166
+ export const start = (
167
+ command: Command.Command
168
+ ): Effect.Effect<CommandExecutor.CommandExecutor, PlatformError, CommandExecutor.Process> =>
169
+ Effect.flatMap(commandExecutor.CommandExecutor, (executor) => executor.start(command))
170
+
171
+ /** @internal */
172
+ export const stream = (
173
+ command: Command.Command
174
+ ): Stream.Stream<CommandExecutor.CommandExecutor, PlatformError, Uint8Array> =>
175
+ Stream.flatMap(commandExecutor.CommandExecutor, (process) => process.stream(command))
176
+
177
+ /** @internal */
178
+ export const streamLines = (
179
+ command: Command.Command
180
+ ): Stream.Stream<CommandExecutor.CommandExecutor, PlatformError, string> =>
181
+ Stream.flatMap(commandExecutor.CommandExecutor, (process) => process.streamLines(command))
182
+
183
+ /** @internal */
184
+ export const string = dual<
185
+ (
186
+ encoding?: string
187
+ ) => (command: Command.Command) => Effect.Effect<CommandExecutor.CommandExecutor, PlatformError, string>,
188
+ (command: Command.Command, encoding?: string) => Effect.Effect<CommandExecutor.CommandExecutor, PlatformError, string>
189
+ >(
190
+ (args) => isCommand(args[0]),
191
+ (command, encoding) =>
192
+ Effect.flatMap(commandExecutor.CommandExecutor, (executor) => executor.string(command, encoding))
193
+ )
194
+
195
+ /** @internal */
196
+ export const workingDirectory: {
197
+ (cwd: string): (self: Command.Command) => Command.Command
198
+ (self: Command.Command, cwd: string): Command.Command
199
+ } = dual<
200
+ (cwd: string) => (self: Command.Command) => Command.Command,
201
+ (self: Command.Command, cwd: string) => Command.Command
202
+ >(2, (self, cwd) => {
203
+ switch (self._tag) {
204
+ case "StandardCommand": {
205
+ return { ...self, cwd: Option.some(cwd) }
206
+ }
207
+ case "PipedCommand": {
208
+ return pipeTo(workingDirectory(self.left, cwd), workingDirectory(self.right, cwd))
209
+ }
210
+ }
211
+ })
@@ -0,0 +1,69 @@
1
+ import * as Brand from "@effect/data/Brand"
2
+ import * as Chunk from "@effect/data/Chunk"
3
+ import { Tag } from "@effect/data/Context"
4
+ import { pipe } from "@effect/data/Function"
5
+ import * as Effect from "@effect/io/Effect"
6
+ import type * as _CommandExecutor from "@effect/platform/CommandExecutor"
7
+ import * as Sink from "@effect/stream/Sink"
8
+ import * as Stream from "@effect/stream/Stream"
9
+
10
+ /** @internal */
11
+ export const ProcessTypeId: _CommandExecutor.ProcessTypeId = Symbol.for(
12
+ "@effect/platform/Process"
13
+ ) as _CommandExecutor.ProcessTypeId
14
+
15
+ /** @internal */
16
+ export const ExitCode = Brand.nominal<_CommandExecutor.ExitCode>()
17
+
18
+ /** @internal */
19
+ export const ProcessId = Brand.nominal<_CommandExecutor.Process.Id>()
20
+
21
+ /** @internal */
22
+ export const CommandExecutor = Tag<_CommandExecutor.CommandExecutor>()
23
+
24
+ /** @internal */
25
+ export const makeExecutor = (start: _CommandExecutor.CommandExecutor["start"]): _CommandExecutor.CommandExecutor => {
26
+ const stream: _CommandExecutor.CommandExecutor["stream"] = (command) =>
27
+ pipe(
28
+ Stream.fromEffect(start(command)),
29
+ Stream.flatMap((process) => process.stdout)
30
+ )
31
+ const streamLines: _CommandExecutor.CommandExecutor["streamLines"] = (command, encoding) => {
32
+ const decoder = new TextDecoder(encoding)
33
+ return Stream.splitLines(
34
+ Stream.mapChunks(stream(command), Chunk.map((bytes) => decoder.decode(bytes)))
35
+ )
36
+ }
37
+ return {
38
+ start,
39
+ exitCode: (command) => Effect.flatMap(start(command), (process) => process.exitCode),
40
+ stream,
41
+ string: (command, encoding = "utf-8") => {
42
+ const decoder = new TextDecoder(encoding)
43
+ return pipe(
44
+ start(command),
45
+ Effect.flatMap((process) => Stream.run(process.stdout, collectUint8Array)),
46
+ Effect.map((bytes) => decoder.decode(bytes))
47
+ )
48
+ },
49
+ lines: (command, encoding = "utf-8") => {
50
+ return pipe(
51
+ streamLines(command, encoding),
52
+ Stream.runCollect,
53
+ Effect.map(Chunk.toReadonlyArray)
54
+ )
55
+ },
56
+ streamLines
57
+ }
58
+ }
59
+
60
+ const collectUint8Array: Sink.Sink<never, never, Uint8Array, never, Uint8Array> = Sink.foldLeftChunks(
61
+ new Uint8Array(),
62
+ (bytes, chunk: Chunk.Chunk<Uint8Array>) =>
63
+ Chunk.reduce(chunk, bytes, (acc, curr) => {
64
+ const newArray = new Uint8Array(acc.length + curr.length)
65
+ newArray.set(acc)
66
+ newArray.set(curr, acc.length)
67
+ return newArray
68
+ })
69
+ )
@@ -41,7 +41,7 @@ const stream = (file: File, {
41
41
  Stream.bufferChunks(
42
42
  Stream.unfoldEffect(offset, (position) => {
43
43
  if (bytesToRead !== undefined && bytesToRead <= position - offset) {
44
- return Effect.succeedNone()
44
+ return Effect.succeed(Option.none())
45
45
  }
46
46
 
47
47
  const toRead = bytesToRead !== undefined && bytesToRead - (position - offset) < chunkSize