diff --git a/_demo/mkdirdemo/mkdir.go b/_demo/mkdirdemo/mkdir.go new file mode 100644 index 00000000..cf9ea4cc --- /dev/null +++ b/_demo/mkdirdemo/mkdir.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + "path/filepath" +) + +func main() { + dirPath := filepath.Join("temp", "myapp", "data", "logs") + os.MkdirAll(dirPath, 0755) +} diff --git a/internal/lib/internal/filepathlite/path.go b/internal/lib/internal/filepathlite/path.go new file mode 100644 index 00000000..2aaed533 --- /dev/null +++ b/internal/lib/internal/filepathlite/path.go @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filepathlite + +import ( + "github.com/goplus/llgo/internal/lib/internal/stringslite" +) + +// FromSlash is filepath.ToSlash. +func FromSlash(path string) string { + if Separator == '/' { + return path + } + return replaceStringByte(path, '/', Separator) +} + +func replaceStringByte(s string, old, new byte) string { + if stringslite.IndexByte(s, old) == -1 { + return s + } + n := []byte(s) + for i := range n { + if n[i] == old { + n[i] = new + } + } + return string(n) +} + +// VolumeName is filepath.VolumeName. +func VolumeName(path string) string { + return FromSlash(path[:volumeNameLen(path)]) +} + +// VolumeNameLen returns the length of the leading volume name on Windows. +// It returns 0 elsewhere. +func VolumeNameLen(path string) int { + return volumeNameLen(path) +} diff --git a/internal/lib/internal/filepathlite/path_unix.go b/internal/lib/internal/filepathlite/path_unix.go new file mode 100644 index 00000000..e2f25598 --- /dev/null +++ b/internal/lib/internal/filepathlite/path_unix.go @@ -0,0 +1,30 @@ +//go:build unix || (js && wasm) || wasip1 + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filepathlite + +const ( + Separator = '/' // OS-specific path separator + ListSeparator = ':' // OS-specific path list separator +) + +// volumeNameLen returns length of the leading volume name on Windows. +// It returns 0 elsewhere. +func volumeNameLen(path string) int { + return 0 +} diff --git a/internal/lib/internal/stringslite/strings.go b/internal/lib/internal/stringslite/strings.go new file mode 100644 index 00000000..4ad916cc --- /dev/null +++ b/internal/lib/internal/stringslite/strings.go @@ -0,0 +1,17 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package stringslite implements a subset of strings, +// only using packages that may be imported by "os". +// +// Tests for these functions are in the strings package. +package stringslite + +import ( + "github.com/goplus/llgo/internal/lib/internal/bytealg" +) + +func IndexByte(s string, c byte) int { + return bytealg.IndexByteString(s, c) +} diff --git a/internal/lib/os/os.go b/internal/lib/os/os.go index cf609934..ebc0b46a 100644 --- a/internal/lib/os/os.go +++ b/internal/lib/os/os.go @@ -271,7 +271,6 @@ func Mkdir(name string, perm FileMode) error { */ // TODO(xsw): -// func MkdirAll(path string, perm FileMode) error // func NewSyscallError(syscall string, err error) error // func ReadFile(name string) ([]byte, error) diff --git a/internal/lib/os/path.go b/internal/lib/os/path.go new file mode 100644 index 00000000..a0bbb720 --- /dev/null +++ b/internal/lib/os/path.go @@ -0,0 +1,63 @@ +package os + +import ( + "syscall" + + "github.com/goplus/llgo/internal/lib/internal/filepathlite" +) + +// MkdirAll creates a directory named path, +// along with any necessary parents, and returns nil, +// or else returns an error. +// The permission bits perm (before umask) are used for all +// directories that MkdirAll creates. +// If path is already a directory, MkdirAll does nothing +// and returns nil. +func MkdirAll(path string, perm FileMode) error { + // Fast path: if we can tell whether path is a directory or file, stop with success or error. + dir, err := Stat(path) + if err == nil { + if dir.IsDir() { + return nil + } + return &PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} + } + + // Slow path: make sure parent exists and then call Mkdir for path. + + // Extract the parent folder from path by first removing any trailing + // path separator and then scanning backward until finding a path + // separator or reaching the beginning of the string. + i := len(path) - 1 + for i >= 0 && IsPathSeparator(path[i]) { + i-- + } + for i >= 0 && !IsPathSeparator(path[i]) { + i-- + } + if i < 0 { + i = 0 + } + + // If there is a parent directory, and it is not the volume name, + // recurse to ensure parent directory exists. + if parent := path[:i]; len(parent) > len(filepathlite.VolumeName(path)) { + err = MkdirAll(parent, perm) + if err != nil { + return err + } + } + + // Parent now exists; invoke Mkdir and use its result. + err = Mkdir(path, perm) + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + return err + } + return nil +}