io/fs: add FormatFileInfo and FormatDirEntry functions

For #54451

Change-Id: I3214066f77b1398ac1f2786ea035c83f32f0a826
Reviewed-on: https://go-review.googlesource.com/c/go/+/489555
Run-TryBot: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
This commit is contained in:
Ian Lance Taylor 2023-04-26 17:44:24 -07:00 committed by Gopher Robot
parent 630ef2edc2
commit 72ba91902a
3 changed files with 201 additions and 0 deletions

2
api/next/54451.txt Normal file
View file

@ -0,0 +1,2 @@
pkg io/fs, func FormatDirEntry(DirEntry) string #54451
pkg io/fs, func FormatFileInfo(FileInfo) string #54451

76
src/io/fs/format.go Normal file
View file

@ -0,0 +1,76 @@
// Copyright 2023 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 fs
import (
"time"
)
// FormatFileInfo returns a formatted version of info for human readability.
// Implementations of FileInfo can call this from a String method.
// The output for a file named "hello.go", 100 bytes, mode 0o644, created
// January 1, 1970 at noon is
//
// -rw-r--r-- 100 1970-01-01 12:00:00 hello.go
func FormatFileInfo(info FileInfo) string {
name := info.Name()
b := make([]byte, 0, 40+len(name))
b = append(b, info.Mode().String()...)
b = append(b, ' ')
size := info.Size()
var usize uint64
if size >= 0 {
usize = uint64(size)
} else {
b = append(b, '-')
usize = uint64(-size)
}
var buf [20]byte
i := len(buf) - 1
for usize >= 10 {
q := usize / 10
buf[i] = byte('0' + usize - q*10)
i--
usize = q
}
buf[i] = byte('0' + usize)
b = append(b, buf[i:]...)
b = append(b, ' ')
b = append(b, info.ModTime().Format(time.DateTime)...)
b = append(b, ' ')
b = append(b, name...)
if info.IsDir() {
b = append(b, '/')
}
return string(b)
}
// FormatDirEntry returns a formatted version of dir for human readability.
// Implementations of DirEntry can call this from a String method.
// The outputs for a directory named subdir and a file named hello.go are:
//
// d subdir/
// - hello.go
func FormatDirEntry(dir DirEntry) string {
name := dir.Name()
b := make([]byte, 0, 5+len(name))
// The Type method does not return any permission bits,
// so strip them from the string.
mode := dir.Type().String()
mode = mode[:len(mode)-9]
b = append(b, mode...)
b = append(b, ' ')
b = append(b, name...)
if dir.IsDir() {
b = append(b, '/')
}
return string(b)
}

123
src/io/fs/format_test.go Normal file
View file

@ -0,0 +1,123 @@
// Copyright 2023 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 fs_test
import (
. "io/fs"
"testing"
"time"
)
// formatTest implements FileInfo to test FormatFileInfo,
// and implements DirEntry to test FormatDirEntry.
type formatTest struct {
name string
size int64
mode FileMode
modTime time.Time
isDir bool
}
func (fs *formatTest) Name() string {
return fs.name
}
func (fs *formatTest) Size() int64 {
return fs.size
}
func (fs *formatTest) Mode() FileMode {
return fs.mode
}
func (fs *formatTest) ModTime() time.Time {
return fs.modTime
}
func (fs *formatTest) IsDir() bool {
return fs.isDir
}
func (fs *formatTest) Sys() any {
return nil
}
func (fs *formatTest) Type() FileMode {
return fs.mode.Type()
}
func (fs *formatTest) Info() (FileInfo, error) {
return fs, nil
}
var formatTests = []struct {
input formatTest
wantFileInfo string
wantDirEntry string
}{
{
formatTest{
name: "hello.go",
size: 100,
mode: 0o644,
modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),
isDir: false,
},
"-rw-r--r-- 100 1970-01-01 12:00:00 hello.go",
"- hello.go",
},
{
formatTest{
name: "home/gopher",
size: 0,
mode: ModeDir | 0o755,
modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),
isDir: true,
},
"drwxr-xr-x 0 1970-01-01 12:00:00 home/gopher/",
"d home/gopher/",
},
{
formatTest{
name: "big",
size: 0x7fffffffffffffff,
mode: ModeIrregular | 0o644,
modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),
isDir: false,
},
"?rw-r--r-- 9223372036854775807 1970-01-01 12:00:00 big",
"? big",
},
{
formatTest{
name: "small",
size: -0x8000000000000000,
mode: ModeSocket | ModeSetuid | 0o644,
modTime: time.Date(1970, time.January, 1, 12, 0, 0, 0, time.UTC),
isDir: false,
},
"Surw-r--r-- -9223372036854775808 1970-01-01 12:00:00 small",
"S small",
},
}
func TestFormatFileInfo(t *testing.T) {
for i, test := range formatTests {
got := FormatFileInfo(&test.input)
if got != test.wantFileInfo {
t.Errorf("%d: FormatFileInfo(%#v) = %q, want %q", i, test.input, got, test.wantFileInfo)
}
}
}
func TestFormatDirEntry(t *testing.T) {
for i, test := range formatTests {
got := FormatDirEntry(&test.input)
if got != test.wantDirEntry {
t.Errorf("%d: FormatDirEntry(%#v) = %q, want %q", i, test.input, got, test.wantDirEntry)
}
}
}