diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 6775cbfaa7..24c930b99a 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -2911,7 +2911,8 @@ let convert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) ( let console (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName with - | "get_Out" -> typedObjExpr t [] |> Some // empty object + | "get_Out" -> Helper.ImportedValue(com, "sys", "stdout", t) |> Some + | "get_Error" -> Helper.ImportedValue(com, "sys", "stderr", t) |> Some | "Write" -> addWarning com ctx.InlinePath r "Write will behave as WriteLine" log com r t i thisArg args |> Some @@ -2919,6 +2920,14 @@ let console (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt | "WriteLine" -> log com r t i thisArg args |> Some | _ -> None +/// System.IO.TextWriter, as returned by Console.Out/Console.Error (sys.stdout/sys.stderr). +let textWriter (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = + match thisArg, i.CompiledName with + | Some writer, "WriteLine" -> + Helper.LibCall(com, "text_writer", "write_line", t, writer :: args, ?loc = r) + |> Some + | _ -> None + let stopwatch (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg with | ".ctor", _ -> @@ -3300,17 +3309,14 @@ let timers (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opti | meth, Some x, args -> Helper.InstanceCall(x, meth, t, args, i.SignatureArgTypes, ?loc = r) |> Some | _ -> None -let systemEnv - (com: ICompiler) - (ctx: Context) - (_: SourceLocation option) - (_: Type) - (i: CallInfo) - (_: Expr option) - (_: Expr list) - = +let systemEnv (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) = match i.CompiledName with | "get_NewLine" -> Some(makeStrConst "\n") + | "GetEnvironmentVariable" -> + let environ = Helper.ImportedValue(com, "os", "environ", Any) + Helper.InstanceCall(environ, "get", t, args, ?loc = r) |> Some + | "get_CurrentDirectory" -> Helper.ImportedCall("os", "getcwd", t, [], ?loc = r) |> Some + | "set_CurrentDirectory" -> Helper.ImportedCall("os", "chdir", t, args, ?loc = r) |> Some | _ -> None // Initial support, making at least InvariantCulture compile-able @@ -3645,6 +3651,9 @@ let paths com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Exp | "HasExtension" as meth -> Helper.LibCall(com, "path", Naming.lowerFirst meth, t, args, i.SignatureArgTypes, ?loc = r) |> Some + | "Combine" -> + Helper.ImportedCall("os.path", "join", t, args, i.SignatureArgTypes, ?loc = r) + |> Some | _ -> None let files com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = @@ -3663,6 +3672,16 @@ let files com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Exp |> Some | _ -> None +let directories com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = + match i.CompiledName with + | "Exists" -> + Helper.ImportedCall("os.path", "isdir", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "CreateDirectory" -> + Helper.LibCall(com, "directory", "create_directory", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | _ -> None + let tasks com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match thisArg, i.CompiledName with | Some x, "GetAwaiter" -> @@ -4145,7 +4164,9 @@ let private replacedModules = "System.Environment", systemEnv "System.Globalization.CultureInfo", globalization "System.IO.File", files + "System.IO.Directory", directories "System.IO.Path", paths + "System.IO.TextWriter", textWriter "System.Random", random "System.Threading.CancellationToken", cancels "System.Threading.CancellationTokenSource", cancels diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 8ae417244c..5bc0792d9d 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -3192,13 +3192,21 @@ let convert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) ( let console (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName with - | "get_Out" -> typedObjExpr t [] |> Some // empty object + | "get_Out" -> Helper.GlobalIdent("console", "log", t) |> Some + | "get_Error" -> Helper.GlobalIdent("console", "error", t) |> Some | "Write" -> addWarning com ctx.InlinePath r "Write will behave as WriteLine" log com r t i thisArg args |> Some | "WriteLine" -> log com r t i thisArg args |> Some | _ -> None +/// System.IO.TextWriter, as returned by Console.Out/Console.Error: `thisArg` +/// is the console.log/console.error function itself, so WriteLine just calls it. +let textWriter (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = + match thisArg, i.CompiledName with + | Some writeFn, "WriteLine" -> Helper.Application(writeFn, t, args, ?loc = r) |> Some + | _ -> None + let debug (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName with | "Write" -> @@ -3532,17 +3540,76 @@ let timers (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opti | meth, Some x, args -> Helper.InstanceCall(x, meth, t, args, i.SignatureArgTypes, ?loc = r) |> Some | _ -> None -let systemEnv - (com: ICompiler) - (ctx: Context) - (_: SourceLocation option) - (_: Type) - (i: CallInfo) - (_: Expr option) - (_: Expr list) - = +let systemEnv (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) = match i.CompiledName with | "get_NewLine" -> Some(makeStrConst "\n") + | "GetEnvironmentVariable" -> + Helper.LibCall(com, "Environment", "getEnvironmentVariable", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "get_CurrentDirectory" -> Helper.GlobalCall("process", t, [], memb = "cwd", ?loc = r) |> Some + | "set_CurrentDirectory" -> Helper.GlobalCall("process", t, args, memb = "chdir", ?loc = r) |> Some + | _ -> None + +let paths (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) = + match i.CompiledName with + | "GetDirectoryName" -> + Helper.ImportedCall("path", "dirname", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "GetExtension" -> + Helper.ImportedCall("path", "extname", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "GetFileName" -> + Helper.ImportedCall("path", "basename", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "GetFileNameWithoutExtension" -> + Helper.LibCall(com, "Path", "getFileNameWithoutExtension", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "GetFullPath" -> + Helper.ImportedCall("path", "resolve", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "GetRandomFileName" -> Helper.LibCall(com, "Path", "getRandomFileName", t, [], ?loc = r) |> Some + | "GetTempFileName" -> Helper.LibCall(com, "Path", "getTempFileName", t, [], ?loc = r) |> Some + | "GetTempPath" -> Helper.ImportedCall("os", "tmpdir", t, [], ?loc = r) |> Some + | "HasExtension" -> + Helper.LibCall(com, "Path", "hasExtension", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "Combine" -> + Helper.ImportedCall("path", "join", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | _ -> None + +let files (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) = + match i.CompiledName with + | "Copy" -> + Helper.ImportedCall("fs", "copyFileSync", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "Delete" -> + Helper.ImportedCall("fs", "unlinkSync", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "Exists" -> + Helper.ImportedCall("fs", "existsSync", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "Move" -> + Helper.ImportedCall("fs", "renameSync", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "ReadAllBytes" -> + Helper.LibCall(com, "File", "readAllBytes", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "ReadAllLines" -> + Helper.LibCall(com, "File", "readAllLines", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "ReadAllText" -> + Helper.LibCall(com, "File", "readAllText", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "WriteAllBytes" -> + Helper.LibCall(com, "File", "writeAllBytes", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "WriteAllLines" -> + Helper.LibCall(com, "File", "writeAllLines", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "WriteAllText" -> + Helper.ImportedCall("fs", "writeFileSync", t, args, i.SignatureArgTypes, ?loc = r) + |> Some | _ -> None // Initial support, making at least InvariantCulture compile-able @@ -4435,6 +4502,9 @@ let private replacedModules = Types.timeOnly, timeOnly Types.timespan, timeSpans "System.Timers.Timer", timers + "System.IO.File", files + "System.IO.Path", paths + "System.IO.TextWriter", textWriter "System.Environment", systemEnv "System.Globalization.CultureInfo", globalization "System.Random", random diff --git a/src/fable-library-py/fable_library/directory.py b/src/fable-library-py/fable_library/directory.py new file mode 100644 index 0000000000..71a3647c01 --- /dev/null +++ b/src/fable-library-py/fable_library/directory.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import os + + +def create_directory(path: str) -> None: + os.makedirs(path, exist_ok=True) + + +__all__ = [ + "create_directory", +] diff --git a/src/fable-library-py/fable_library/text_writer.py b/src/fable-library-py/fable_library/text_writer.py new file mode 100644 index 0000000000..6dd5aa5b0c --- /dev/null +++ b/src/fable-library-py/fable_library/text_writer.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from typing import TextIO + + +def write_line(writer: TextIO, text: str = "") -> None: + writer.write(text + "\n") + + +__all__ = ["write_line"] diff --git a/src/fable-library-ts/Environment.ts b/src/fable-library-ts/Environment.ts new file mode 100644 index 0000000000..a186256b41 --- /dev/null +++ b/src/fable-library-ts/Environment.ts @@ -0,0 +1,7 @@ +// process.env is Node-specific; there is no browser equivalent for reading +// environment variables, so this only works when running under Node. +declare const process: { env: Record }; + +export function getEnvironmentVariable(name: string): string { + return process.env[name] as string; +} diff --git a/src/fable-library-ts/File.ts b/src/fable-library-ts/File.ts new file mode 100644 index 0000000000..114a444fc3 --- /dev/null +++ b/src/fable-library-ts/File.ts @@ -0,0 +1,35 @@ +// This library doesn't depend on @types/node (to stay browser-compatible), +// so this Node.js built-in import can't be type-checked; the exported +// functions below still declare their own types for callers to rely on. +// @ts-ignore +import { readFileSync, writeFileSync } from "node:fs"; +import { MutableArray } from "./Util.ts"; + +// byte[] is represented as a plain JS/TS array of numbers, not a typed array +// (see e.g. BitConverter.ts), so convert the Buffer returned by readFileSync. +export function readAllBytes(path: string): number[] { + return Array.from(readFileSync(path)); +} + +export function readAllLines(path: string): string[] { + const lines = readFileSync(path, "utf8").split(/\r\n|\r|\n/); + + if (lines[lines.length - 1] === "") { + lines.pop(); + } + + return lines; +} + +export function readAllText(path: string): string { + return readFileSync(path, "utf8"); +} + +export function writeAllBytes(path: string, bytes: MutableArray): void { + // Convert back from the plain array representation to a typed array Node's fs accepts. + writeFileSync(path, Uint8Array.from(bytes)); +} + +export function writeAllLines(path: string, lines: MutableArray): void { + writeFileSync(path, lines.join("\n")); +} diff --git a/src/fable-library-ts/Path.ts b/src/fable-library-ts/Path.ts new file mode 100644 index 0000000000..6ce1a9e207 --- /dev/null +++ b/src/fable-library-ts/Path.ts @@ -0,0 +1,37 @@ +// This library doesn't depend on @types/node (to stay browser-compatible), +// so these Node.js built-in imports can't be type-checked; the exported +// functions below still declare their own types for callers to rely on. +// @ts-ignore +import { basename, extname, join } from "node:path"; +// @ts-ignore +import { tmpdir } from "node:os"; +// @ts-ignore +import { writeFileSync } from "node:fs"; + +// Mirrors the format of .NET's Path.GetRandomFileName(): 8 random hex chars, +// a dot, then 3 more random hex chars. +export function getRandomFileName(): string { + const nameBytes = new Uint8Array(4); + crypto.getRandomValues(nameBytes); + const name = Array.from(nameBytes, (b) => b.toString(16).padStart(2, "0")).join(""); + + const extBytes = new Uint8Array(2); + crypto.getRandomValues(extBytes); + const ext = Array.from(extBytes, (b) => b.toString(16).padStart(2, "0")).join("").slice(0, 3); + + return `${name}.${ext}`; +} + +export function getFileNameWithoutExtension(path: string): string { + return basename(path, extname(path)); +} + +export function hasExtension(path: string): boolean { + return extname(path) !== ""; +} + +export function getTempFileName(): string { + const path = join(tmpdir(), getRandomFileName()); + writeFileSync(path, ""); + return path; +} diff --git a/src/quicktest-py/quicktest.fsx b/src/quicktest-py/quicktest.fsx index d0fb0f6474..407d3b776b 100644 --- a/src/quicktest-py/quicktest.fsx +++ b/src/quicktest-py/quicktest.fsx @@ -45,6 +45,14 @@ let main argv = // use file = builtins.``open``(StringPath "data.txt") // file.read() |> printfn "File contents: %s" + System.Environment.GetEnvironmentVariable("__FABLE_NON_EXISTENT_VAR__") + |> equal null + + System.Environment.CurrentDirectory.Length > 0 |> equal true + + System.Console.Out.WriteLine("Hello stdout") + System.Console.Error.WriteLine("Hello stderr") + printfn "All tests passed!" 0 diff --git a/src/quicktest/QuickTest.fs b/src/quicktest/QuickTest.fs index 848758087f..f83ade04cb 100644 --- a/src/quicktest/QuickTest.fs +++ b/src/quicktest/QuickTest.fs @@ -99,3 +99,68 @@ printfn "Running quick tests..." // to Fable.Tests project. For example: // testCase "Addition works" <| fun () -> // 2 + 2 |> equal 4 + +testCase "Path methods work" +<| fun () -> + System.IO.Path.GetDirectoryName("temp/test.txt") |> equal "temp" + System.IO.Path.GetExtension("temp/test.txt") |> equal ".txt" + System.IO.Path.GetFileName("temp/test.txt") |> equal "test.txt" + System.IO.Path.GetFileNameWithoutExtension("temp/test.txt") |> equal "test" + System.IO.Path.HasExtension("temp/test.txt") |> equal true + System.IO.Path.HasExtension("temp/test") |> equal false + (System.IO.Path.GetFullPath("temp/test.txt")).Length > 0 |> equal true + (System.IO.Path.GetTempPath()).Length > 0 |> equal true + (System.IO.Path.GetRandomFileName()).Length > 0 |> equal true + +testCase "File methods work" +<| fun () -> + let file = + System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()) + + System.IO.File.Exists(file) |> equal false + + System.IO.File.WriteAllText(file, "Hello World") + System.IO.File.Exists(file) |> equal true + System.IO.File.ReadAllText(file) |> equal "Hello World" + + System.IO.File.WriteAllLines(file, [| "Hello"; "World" |]) + let lines = System.IO.File.ReadAllLines(file) + lines |> equal [| "Hello"; "World" |] + + let bytes = System.IO.File.ReadAllBytes(file) + bytes.Length > 0 |> equal true + System.IO.File.WriteAllBytes(file, bytes) + + let file2 = file + ".copy" + System.IO.File.Copy(file, file2) + System.IO.File.Exists(file2) |> equal true + + let file3 = file + ".moved" + System.IO.File.Move(file2, file3) + System.IO.File.Exists(file2) |> equal false + System.IO.File.Exists(file3) |> equal true + + System.IO.File.Delete(file) + System.IO.File.Delete(file3) + System.IO.File.Exists(file) |> equal false + System.IO.File.Exists(file3) |> equal false + +testCase "Path.GetTempFileName works" +<| fun () -> + let file1 = System.IO.Path.GetTempFileName() + let file2 = System.IO.Path.GetTempFileName() + System.IO.File.Exists(file1) |> equal true + System.IO.File.Exists(file2) |> equal true + (file1 = file2) |> equal false + System.IO.File.Delete(file1) + System.IO.File.Delete(file2) + +testCase "Environment/Console methods work" +<| fun () -> + System.Environment.GetEnvironmentVariable("__FABLE_NON_EXISTENT_VAR__") + |> equal null + + System.Environment.CurrentDirectory.Length > 0 |> equal true + + System.Console.Out.WriteLine("Hello stdout") + System.Console.Error.WriteLine("Hello stderr") diff --git a/tests/Js/Main/EnvironmentTests.fs b/tests/Js/Main/EnvironmentTests.fs new file mode 100644 index 0000000000..8f12a407f3 --- /dev/null +++ b/tests/Js/Main/EnvironmentTests.fs @@ -0,0 +1,19 @@ +module Fable.Tests.Environment + +open System +open Util.Testing + +let tests = + testList "Environment" [ + testCase "Environment.GetEnvironmentVariable returns null for a missing variable" <| fun () -> + Environment.GetEnvironmentVariable("__FABLE_NON_EXISTENT_VAR__") + |> equal null + + testCase "Environment.CurrentDirectory works" <| fun () -> + Environment.CurrentDirectory.Length > 0 + |> equal true + + testCase "Console.Out.WriteLine works" <| fun () -> Console.Out.WriteLine("Hello stdout") + + testCase "Console.Error.WriteLine works" <| fun () -> Console.Error.WriteLine("Hello stderr") + ] diff --git a/tests/Js/Main/Fable.Tests.fsproj b/tests/Js/Main/Fable.Tests.fsproj index 0602b9333b..933d6f4d4f 100644 --- a/tests/Js/Main/Fable.Tests.fsproj +++ b/tests/Js/Main/Fable.Tests.fsproj @@ -55,6 +55,7 @@ + @@ -69,6 +70,7 @@ + diff --git a/tests/Js/Main/Main.fs b/tests/Js/Main/Main.fs index 14432b551c..f42d889826 100644 --- a/tests/Js/Main/Main.fs +++ b/tests/Js/Main/Main.fs @@ -24,6 +24,7 @@ let allTests = #endif Enumerable.tests Enum.tests + Environment.tests Event.tests HashSets.tests Import.tests @@ -36,6 +37,7 @@ let allTests = NestedAndRecursivePatternTests.tests Observable.tests Option.tests + SystemIO.tests Queue.tests RecordTypes.tests Reflection.tests diff --git a/tests/Js/Main/SystemIOTests.fs b/tests/Js/Main/SystemIOTests.fs new file mode 100644 index 0000000000..3b40715361 --- /dev/null +++ b/tests/Js/Main/SystemIOTests.fs @@ -0,0 +1,137 @@ +module Fable.Tests.SystemIO + +open System.IO +open Util.Testing + +let tests = + testList "SystemIO" [ + testList "Path" [ + testCase "Path.Combine works with two parts" <| fun () -> + let combined = Path.Combine("foo", "bar") + Path.GetFileName(combined) |> equal "bar" + Path.GetDirectoryName(combined) |> equal "foo" + + testCase "Path.Combine works with three parts" <| fun () -> + let combined = Path.Combine("foo", "bar", "baz.txt") + Path.GetFileName(combined) |> equal "baz.txt" + Path.GetDirectoryName(combined) |> equal (Path.Combine("foo", "bar")) + + testCase "Path.GetDirectoryName works" <| fun () -> + Path.GetDirectoryName("temp/test.txt") + |> equal "temp" + + testCase "Path.GetExtension works" <| fun () -> + Path.GetExtension("temp/test.txt") + |> equal ".txt" + + testCase "Path.GetFileName works" <| fun () -> + Path.GetFileName("temp/test.txt") + |> equal "test.txt" + + testCase "Path.GetFileNameWithoutExtension works" <| fun () -> + Path.GetFileNameWithoutExtension("temp/test.txt") + |> equal "test" + + testCase "Path.HasExtension works" <| fun () -> + Path.HasExtension("temp/test.txt") + |> equal true + + Path.HasExtension("temp/test") + |> equal false + + testCase "Path.GetFullPath works" <| fun () -> + (Path.GetFullPath("temp/test.txt")).Length > 0 + |> equal true + + testCase "Path.GetTempPath works" <| fun () -> + (Path.GetTempPath()).Length > 0 + |> equal true + + testCase "Path.GetRandomFileName works" <| fun () -> + (Path.GetRandomFileName()).Length > 0 + |> equal true + + testCase "Path.GetTempFileName works" <| fun () -> + let file1 = Path.GetTempFileName() + let file2 = Path.GetTempFileName() + + File.Exists(file1) |> equal true + File.Exists(file2) |> equal true + (file1 = file2) |> equal false + + File.Delete(file1) + File.Delete(file2) + ] + + testList "File" [ + testCase "File.Exists works" <| fun () -> + let file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + File.Exists(file) |> equal false + + File.WriteAllText(file, "Hello World") + File.Exists(file) |> equal true + + File.Delete(file) + File.Exists(file) |> equal false + + testCase "File.WriteAllText and ReadAllText work" <| fun () -> + let file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + File.WriteAllText(file, "Hello World") + + File.ReadAllText(file) + |> equal "Hello World" + + File.Delete(file) + + testCase "File.WriteAllLines and ReadAllLines work" <| fun () -> + let file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + File.WriteAllLines(file, [| "Hello"; "World" |]) + + File.ReadAllLines(file) + |> equal [| "Hello"; "World" |] + + File.Delete(file) + + testCase "File.ReadAllLines handles CRLF and a trailing newline" <| fun () -> + let file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + File.WriteAllText(file, "Hello\r\nWorld\r\n") + + File.ReadAllLines(file) + |> equal [| "Hello"; "World" |] + + File.Delete(file) + + testCase "File.WriteAllBytes and ReadAllBytes work" <| fun () -> + let file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + let bytes = [| 1uy; 2uy; 3uy |] + File.WriteAllBytes(file, bytes) + + File.ReadAllBytes(file) + |> equal bytes + + File.Delete(file) + + testCase "File.Copy works" <| fun () -> + let file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + let copy = file + ".copy" + File.WriteAllText(file, "Hello World") + File.Copy(file, copy) + + File.Exists(copy) |> equal true + File.ReadAllText(copy) |> equal "Hello World" + + File.Delete(file) + File.Delete(copy) + + testCase "File.Move works" <| fun () -> + let file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + let moved = file + ".moved" + File.WriteAllText(file, "Hello World") + File.Move(file, moved) + + File.Exists(file) |> equal false + File.Exists(moved) |> equal true + + File.Delete(moved) + ] + ] diff --git a/tests/Python/TestSystemIO.fs b/tests/Python/TestSystemIO.fs index 052b2042f7..4c04d5be01 100644 --- a/tests/Python/TestSystemIO.fs +++ b/tests/Python/TestSystemIO.fs @@ -54,6 +54,18 @@ let ``test get extension works`` () = ext |> equal ".txt" +[] +let ``test combine works`` () = + let path = System.IO.Path.Combine("temp", "test.txt") + System.IO.Path.GetFileName(path) |> equal "test.txt" + System.IO.Path.GetDirectoryName(path) |> equal "temp" + +[] +let ``test combine with three parts works`` () = + let path = System.IO.Path.Combine("temp", "sub", "test.txt") + System.IO.Path.GetFileName(path) |> equal "test.txt" + System.IO.Path.GetDirectoryName(path) |> equal (System.IO.Path.Combine("temp", "sub")) + [] let ``test get full path works`` () = let path = System.IO.Path.GetFullPath("temp/test.txt") @@ -90,3 +102,55 @@ let ``test file exists works`` () = System.IO.File.Delete(file) System.IO.File.Exists(file) |> equal false + +[] +let ``test directory exists works`` () = + System.IO.Directory.Exists(System.IO.Path.GetTempPath()) + |> equal true + +[] +let ``test directory exists returns false for non-existing directory`` () = + let dir = + System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()) + + System.IO.Directory.Exists(dir) + |> equal false + +[] +let ``test directory exists returns false for a file path`` () = + let file = System.IO.Path.GetTempFileName() + System.IO.Directory.Exists(file) + |> equal false + System.IO.File.Delete(file) + +[] +let ``test create directory creates missing parent directories`` () = + let baseDir = + System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()) + + let nested = System.IO.Path.Combine(baseDir, "a", "b", "c") + + System.IO.Directory.Exists(nested) + |> equal false + + System.IO.Directory.CreateDirectory(nested) + |> ignore + + System.IO.Directory.Exists(nested) + |> equal true + System.IO.Directory.Exists(baseDir) + |> equal true + +[] +let ``test create directory does not throw when directory already exists`` () = + let dir = + System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()) + + System.IO.Directory.CreateDirectory(dir) + |> ignore + + System.IO.Directory.CreateDirectory(dir) + |> ignore + + System.IO.Directory.Exists(dir) + |> equal true diff --git a/tests/TypeScript/Fable.Tests.TypeScript.fsproj b/tests/TypeScript/Fable.Tests.TypeScript.fsproj index 5bd1587c65..580c200cc9 100644 --- a/tests/TypeScript/Fable.Tests.TypeScript.fsproj +++ b/tests/TypeScript/Fable.Tests.TypeScript.fsproj @@ -45,6 +45,7 @@ + @@ -59,6 +60,7 @@ + diff --git a/tests/TypeScript/Main.fs b/tests/TypeScript/Main.fs index c6ab38b294..b4b28ff389 100644 --- a/tests/TypeScript/Main.fs +++ b/tests/TypeScript/Main.fs @@ -24,6 +24,7 @@ let allTests = #endif Enumerable.tests Enum.tests + Environment.tests Event.tests HashSets.tests // Import.tests @@ -35,6 +36,7 @@ let allTests = NestedAndRecursivePatternTests.tests Observable.tests Option.tests + SystemIO.tests Queue.tests RecordTypes.tests Reflection.tests