Skip to content
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
41 changes: 31 additions & 10 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2911,14 +2911,23 @@ 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
| "ReadLine" -> Helper.GlobalCall("input", t, args, ?loc = r) |> Some
| "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", _ ->
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) =
Expand All @@ -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" ->
Expand Down Expand Up @@ -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
Expand Down
90 changes: 80 additions & 10 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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" ->
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions src/fable-library-py/fable_library/directory.py
Original file line number Diff line number Diff line change
@@ -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",
]
10 changes: 10 additions & 0 deletions src/fable-library-py/fable_library/text_writer.py
Original file line number Diff line number Diff line change
@@ -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"]
7 changes: 7 additions & 0 deletions src/fable-library-ts/Environment.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | undefined> };

export function getEnvironmentVariable(name: string): string {
return process.env[name] as string;
}
35 changes: 35 additions & 0 deletions src/fable-library-ts/File.ts
Original file line number Diff line number Diff line change
@@ -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<number>): 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<string>): void {
writeFileSync(path, lines.join("\n"));
}
37 changes: 37 additions & 0 deletions src/fable-library-ts/Path.ts
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 8 additions & 0 deletions src/quicktest-py/quicktest.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
65 changes: 65 additions & 0 deletions src/quicktest/QuickTest.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Loading
Loading