Skip to content

Commit 09cb36b

Browse files
github-actions[bot]Copilotdbrattliclaude
authored
feat(stdlib): add os.walk, makedirs(exist_ok), getpid, getppid and os.path improvements (#287)
* feat(stdlib): add os.walk, makedirs(exist_ok), getpid, getppid and os.path improvements Add missing but commonly-needed functions to the Os module: - os.makedirs(path, exist_ok) overload using keyword arg emission so callers can write os.makedirs(dir, true) without specifying mode - os.walk / os.walk(topdown) for directory tree traversal - os.getpid() and os.getppid() to query current/parent process IDs - os.path.isabs to test if a path is absolute - os.path.islink to test if a path is a symbolic link - os.path.realpath to resolve symlinks and canonicalize a path - os.path.getsize to get the size of a file in bytes Also adds 7 new tests to TestOs.fs covering all new members. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(stdlib): address review feedback on Os bindings - Change os.path.getsize return type from int to int64 to handle files >= 2 GiB - Reorder getpid/getppid next to getenv; move walk near rmdir for grouping - Improve walk test to assert dirpath="." and that subdirs+files match listdir - Add getppid test - Add walk(topdown=false) test using a controlled temp dir - Use unique /tmp paths and clean up after makedirs/walk tests - Revert CHANGELOG.md addition (AGENTS.md forbids modifying CHANGELOG) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dag Brattli <dag@brattli.net> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 180c086 commit 09cb36b

2 files changed

Lines changed: 103 additions & 1 deletion

File tree

src/stdlib/Os.fs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ type IExports =
3030
/// Return the value of the environment variable key or default if not set
3131
/// See https://docs.python.org/3/library/os.html#os.getenv
3232
abstract getenv: key: string * ``default``: string -> string
33+
/// Return the current process id.
34+
/// See https://docs.python.org/3/library/os.html#os.getpid
35+
abstract getpid: unit -> int
36+
/// Return the parent process id.
37+
/// See https://docs.python.org/3/library/os.html#os.getppid
38+
abstract getppid: unit -> int
3339
/// Send signal sig to the process pid
3440
/// See https://docs.python.org/3/library/os.html#os.kill
3541
abstract kill: pid: int * ``sig``: int -> unit
@@ -42,8 +48,14 @@ type IExports =
4248
/// Recursive directory creation function
4349
/// See https://docs.python.org/3/library/os.html#os.makedirs
4450
abstract makedirs: path: string -> unit
45-
/// Recursive directory creation with optional mode and exist_ok flag
51+
/// Recursive directory creation, creating parent directories as needed.
52+
/// Raises FileExistsError if the directory already exists and exist_ok is false.
4653
/// See https://docs.python.org/3/library/os.html#os.makedirs
54+
[<Emit("$0.makedirs($1, exist_ok=$2)")>]
55+
abstract makedirs: path: string * exist_ok: bool -> unit
56+
/// Recursive directory creation with explicit mode and exist_ok flag.
57+
/// See https://docs.python.org/3/library/os.html#os.makedirs
58+
[<Emit("$0.makedirs($1, $2, $3)")>]
4759
abstract makedirs: path: string * mode: int * exist_ok: bool -> unit
4860
/// Set the environment variable named key to the string value
4961
/// See https://docs.python.org/3/library/os.html#os.putenv
@@ -60,6 +72,15 @@ type IExports =
6072
/// Remove (delete) the directory path
6173
/// See https://docs.python.org/3/library/os.html#os.rmdir
6274
abstract rmdir: path: string -> unit
75+
/// Walk a directory tree, yielding (dirpath, dirnames, filenames) for each directory.
76+
/// When topdown is true (the default) the caller can modify the dirnames list in-place
77+
/// to prune the search or impose a specific visiting order.
78+
/// See https://docs.python.org/3/library/os.html#os.walk
79+
abstract walk: top: string -> seq<string * ResizeArray<string> * ResizeArray<string>>
80+
/// Walk a directory tree top-down or bottom-up (topdown=false).
81+
/// See https://docs.python.org/3/library/os.html#os.walk
82+
[<Emit("$0.walk($1, topdown=$2)")>]
83+
abstract walk: top: string * topdown: bool -> seq<string * ResizeArray<string> * ResizeArray<string>>
6384
/// Test whether a path exists
6485
/// See https://docs.python.org/3/library/os.path.html#os.path.exists
6586
abstract path: PathModule
@@ -96,6 +117,18 @@ and [<Erase>] PathModule =
96117
/// Split the pathname path into a pair (root, ext)
97118
/// See https://docs.python.org/3/library/os.path.html#os.path.splitext
98119
abstract splitext: path: string -> string * string
120+
/// Return True if path is an absolute pathname
121+
/// See https://docs.python.org/3/library/os.path.html#os.path.isabs
122+
abstract isabs: path: string -> bool
123+
/// Return True if path refers to a symbolic link
124+
/// See https://docs.python.org/3/library/os.path.html#os.path.islink
125+
abstract islink: path: string -> bool
126+
/// Return the canonical path of the specified filename, resolving symlinks
127+
/// See https://docs.python.org/3/library/os.path.html#os.path.realpath
128+
abstract realpath: path: string -> string
129+
/// Return the size, in bytes, of path
130+
/// See https://docs.python.org/3/library/os.path.html#os.path.getsize
131+
abstract getsize: path: string -> int64
99132

100133

101134
/// Miscellaneous operating system interfaces

test/TestOs.fs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,72 @@ let ``test os.listdir works`` () =
5858
let entries = os.listdir "."
5959
// Current directory should have at least some entries
6060
entries.Length > 0 |> equal true
61+
62+
[<Fact>]
63+
let ``test os.path.isabs works`` () =
64+
os.path.isabs "/absolute/path" |> equal true
65+
os.path.isabs "relative/path" |> equal false
66+
os.path.isabs "." |> equal false
67+
68+
[<Fact>]
69+
let ``test os.path.realpath works`` () =
70+
let real = os.path.realpath "."
71+
real.StartsWith("/") |> equal true
72+
// realpath should not contain symlink components; at minimum equal to abspath for "."
73+
real.Length > 0 |> equal true
74+
75+
[<Fact>]
76+
let ``test os.path.islink works`` () =
77+
// "." is never a symlink
78+
os.path.islink "." |> equal false
79+
80+
[<Fact>]
81+
let ``test os.path.getsize works`` () =
82+
// The test directory itself has a positive size
83+
os.path.getsize "." > 0L |> equal true
84+
85+
[<Fact>]
86+
let ``test os.getpid works`` () =
87+
let pid = os.getpid ()
88+
pid > 0 |> equal true
89+
90+
[<Fact>]
91+
let ``test os.getppid works`` () =
92+
let ppid = os.getppid ()
93+
ppid > 0 |> equal true
94+
95+
[<Fact>]
96+
let ``test os.makedirs with exist_ok works`` () =
97+
let dir = sprintf "/tmp/fable_test_makedirs_%d" (os.getpid ())
98+
os.makedirs (dir, true)
99+
os.path.isdir dir |> equal true
100+
// Second call must not raise when exist_ok=true
101+
os.makedirs (dir, true)
102+
os.path.isdir dir |> equal true
103+
os.rmdir dir
104+
105+
[<Fact>]
106+
let ``test os.walk yields entries`` () =
107+
let entries = os.walk "." |> Seq.truncate 1 |> Seq.toList
108+
entries.Length |> equal 1
109+
let dirpath, subdirs, files = entries.[0]
110+
dirpath |> equal "."
111+
// The first walk entry's dirnames + filenames should equal listdir of the root
112+
let walkAll = Seq.append subdirs files |> Seq.sort |> Seq.toList
113+
let listdirAll = os.listdir "." |> Array.sort |> Array.toList
114+
walkAll |> equal listdirAll
115+
116+
[<Fact>]
117+
let ``test os.walk with topdown=false works`` () =
118+
let root = sprintf "/tmp/fable_test_walk_%d" (os.getpid ())
119+
let nested = os.path.join (root, "nested")
120+
os.makedirs (nested, true)
121+
let entries = os.walk (root, false) |> Seq.toList
122+
// Bottom-up: deepest dir is yielded first, root is yielded last
123+
entries.Length |> equal 2
124+
let firstDir, _, _ = entries.[0]
125+
let lastDir, _, _ = List.last entries
126+
firstDir |> equal nested
127+
lastDir |> equal root
128+
os.rmdir nested
129+
os.rmdir root

0 commit comments

Comments
 (0)