diff --git a/README.md b/README.md new file mode 100644 index 0000000..12e82c0 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Pathname + +This is a implementation of Ruby's Pathname class. diff --git a/go.mod b/go.mod index ca72020..fb83599 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module github.com/mai7star/pathname go 1.24.4 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..713a0b4 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pathname.go b/pathname.go new file mode 100644 index 0000000..eb896e9 --- /dev/null +++ b/pathname.go @@ -0,0 +1,126 @@ +package pathname + +import ( + "os" + "path/filepath" + "slices" +) + +type Pathname struct { + path string +} + +func NewPathname(path string) Pathname { + return Pathname{ + path: path, + } +} + +func NewPathnamePtr(path string) *Pathname { + pathname := NewPathname(path) + return &pathname +} + +func (p *Pathname) ToPath() string { + if p == nil { + return "" + } + return p.path +} + +func (p *Pathname) IsAbsolute() bool { + if p == nil || p.path == "" { + return false + } + return p.path[0] == '/' +} + +func (p *Pathname) IsRelative() bool { + if p == nil || p.path == "" { + return false + } + return p.path[0] != '/' +} + +func (p *Pathname) IsRoot() bool { + if p == nil || p.path == "" { + return false + } + return p.path == "/" +} + +func (p *Pathname) IsExist() bool { + if p == nil { + return false + } + + _, err := os.Stat(p.path) + return err == nil +} + +func (p *Pathname) IsFile() bool { + if p == nil { + return false + } + + info, err := os.Stat(p.path) + if err != nil { + return false + } + + return !info.IsDir() +} + +func (p *Pathname) IsDirectory() bool { + if p == nil { + return false + } + + info, err := os.Stat(p.path) + if err != nil { + return false + } + + return info.IsDir() +} + +func (p *Pathname) Basename() *Pathname { + if p == nil { + return nil + } + + path := filepath.Base(p.path) + return NewPathnamePtr(path) +} + +func (p *Pathname) Dirname() *Pathname { + if p == nil { + return nil + } + + path := filepath.Dir(p.path) + return NewPathnamePtr(path) +} + +func (p *Pathname) Join(parts ...string) *Pathname { + if p == nil { + return nil + } + + parts = slices.Concat([]string{p.path}, parts) + path := filepath.Join(parts...) + return NewPathnamePtr(path) +} + +func (p *Pathname) OpenFile(flag int, perm os.FileMode) (*os.File, error) { + return os.OpenFile(p.ToPath(), flag, perm) +} + +func (p *Pathname) Size() (int, error) { + info, err := os.Stat(p.ToPath()) + if err != nil { + return 0, err + } + + return int(info.Size()), nil +} diff --git a/pathname_test.go b/pathname_test.go new file mode 100644 index 0000000..6701f79 --- /dev/null +++ b/pathname_test.go @@ -0,0 +1,292 @@ +package pathname + +import ( + "fmt" + "io/fs" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewPathname(t *testing.T) { + assert.NoError(t, nil) +} + +func TestPathname_ToPath(t *testing.T) { + var p *Pathname + assert.Equal(t, "", p.ToPath()) + + testcases := []struct { + p Pathname + expected string + }{ + {NewPathname(""), ""}, + {NewPathname("/path"), "/path"}, + {NewPathname("path"), "path"}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.ToPath()) + }) + } +} + +func TestPathname_IsAbsolute(t *testing.T) { + var p *Pathname + assert.False(t, p.IsAbsolute()) + + testcases := []struct { + p Pathname + expected bool + }{ + {NewPathname(""), false}, + {NewPathname("/"), true}, + {NewPathname("/path"), true}, + {NewPathname("."), false}, + {NewPathname("path"), false}, + {NewPathname("./path"), false}, + {NewPathname("../path"), false}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.IsAbsolute()) + }) + } +} + +func TestPathname_IsRelative(t *testing.T) { + var p *Pathname + assert.False(t, p.IsRelative()) + + testcases := []struct { + p Pathname + expected bool + }{ + {NewPathname(""), false}, + {NewPathname("/"), false}, + {NewPathname("/path"), false}, + {NewPathname("."), true}, + {NewPathname("path"), true}, + {NewPathname("./path"), true}, + {NewPathname("../path"), true}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.IsRelative()) + }) + } +} + +func TestPathname_IsRoot(t *testing.T) { + var p *Pathname + assert.False(t, p.IsRoot()) + + testcases := []struct { + p Pathname + expected bool + }{ + {NewPathname(""), false}, + {NewPathname("/"), true}, + {NewPathname("/path"), false}, + {NewPathname("/."), false}, + {NewPathname("/path/.."), false}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.IsRoot()) + }) + } +} + +func TestPathname_IsExist(t *testing.T) { + var p *Pathname + assert.False(t, p.IsExist()) + + testcases := []struct { + p Pathname + expected bool + }{ + {NewPathname(""), false}, + {NewPathname("/"), true}, + {NewPathname("."), true}, + {NewPathname("testdata/file1.txt"), true}, + {NewPathname("./testdata/file1.txt"), true}, + {NewPathname("testdata/dir1"), true}, + {NewPathname("./testdata/dir1"), true}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.IsExist()) + }) + } +} + +func TestPathname_IsFile(t *testing.T) { + var p *Pathname + assert.False(t, p.IsFile()) + + testcases := []struct { + p Pathname + expected bool + }{ + {NewPathname(""), false}, + {NewPathname("/"), false}, + {NewPathname("."), false}, + {NewPathname("testdata/file1.txt"), true}, + {NewPathname("./testdata/file1.txt"), true}, + {NewPathname("testdata/dir1"), false}, + {NewPathname("./testdata/dir1"), false}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.IsFile()) + }) + } +} + +func TestPathname_IsDirectory(t *testing.T) { + var p *Pathname + assert.False(t, p.IsDirectory()) + + testcases := []struct { + p Pathname + expected bool + }{ + {NewPathname(""), false}, + {NewPathname("/"), true}, + {NewPathname("."), true}, + {NewPathname("testdata/file1.txt"), false}, + {NewPathname("./testdata/file1.txt"), false}, + {NewPathname("testdata/dir1"), true}, + {NewPathname("./testdata/dir1"), true}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.IsDirectory()) + }) + } +} + +func TestPathname_Basename(t *testing.T) { + var p *Pathname + assert.Equal(t, p, p.Dirname()) + + testcases := []struct { + p Pathname + expected *Pathname + }{ + {NewPathname(""), NewPathnamePtr(".")}, + {NewPathname("/"), NewPathnamePtr("/")}, + {NewPathname("."), NewPathnamePtr(".")}, + {NewPathname("testdata/file1.txt"), NewPathnamePtr("file1.txt")}, + {NewPathname("./testdata/file1.txt"), NewPathnamePtr("file1.txt")}, + {NewPathname("testdata/dir1"), NewPathnamePtr("dir1")}, + {NewPathname("testdata/dir1/"), NewPathnamePtr("dir1")}, + {NewPathname("./testdata/dir1"), NewPathnamePtr("dir1")}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.Basename()) + }) + } +} + +func TestPathname_Dirname(t *testing.T) { + var p *Pathname + assert.Equal(t, p, p.Dirname()) + + testcases := []struct { + p Pathname + expected *Pathname + }{ + {NewPathname(""), NewPathnamePtr(".")}, + {NewPathname("/"), NewPathnamePtr("/")}, + {NewPathname("."), NewPathnamePtr(".")}, + {NewPathname("testdata/file1.txt"), NewPathnamePtr("testdata")}, + {NewPathname("./testdata/file1.txt"), NewPathnamePtr("testdata")}, + {NewPathname("testdata/dir1"), NewPathnamePtr("testdata")}, + {NewPathname("testdata/dir1/"), NewPathnamePtr("testdata/dir1")}, + {NewPathname("./testdata/dir1"), NewPathnamePtr("testdata")}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.Dirname()) + }) + } +} + +func TestPathname_Join(t *testing.T) { + var p *Pathname + assert.Equal(t, p, p.Join()) + + testcases := []struct { + p Pathname + parts []string + expected *Pathname + }{ + {NewPathname(""), []string{}, NewPathnamePtr("")}, + {NewPathname(""), []string{"path", "file.txt"}, NewPathnamePtr("path/file.txt")}, + {NewPathname("/"), []string{"."}, NewPathnamePtr("/")}, + {NewPathname("/"), []string{".."}, NewPathnamePtr("/")}, + {NewPathname("/"), []string{"path", "file.txt"}, NewPathnamePtr("/path/file.txt")}, + {NewPathname("/"), []string{"path", ".", "..", "file.txt"}, NewPathnamePtr("/file.txt")}, + {NewPathname("/"), []string{"path", "..", "..", "file.txt"}, NewPathnamePtr("/file.txt")}, + {NewPathname("."), []string{}, NewPathnamePtr(".")}, + {NewPathname("testdata/file1.txt"), []string{}, NewPathnamePtr("testdata/file1.txt")}, + {NewPathname("./testdata/file1.txt"), []string{}, NewPathnamePtr("testdata/file1.txt")}, + {NewPathname("testdata/dir1"), []string{}, NewPathnamePtr("testdata/dir1")}, + {NewPathname("testdata/dir1/"), []string{}, NewPathnamePtr("testdata/dir1")}, + {NewPathname("./testdata/dir1"), []string{}, NewPathnamePtr("testdata/dir1")}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + assert.Equal(t, tc.expected, tc.p.Join(tc.parts...)) + }) + } +} + +func TestPathname_OpenFile(t *testing.T) { + var p *Pathname + file, err := p.OpenFile(os.O_RDONLY, 0) + assert.Nil(t, file) + var expectedError *os.PathError + assert.ErrorAs(t, err, &expectedError) + + testcases := []struct { + p Pathname + expectedErr error + }{ + {NewPathname("./testdata/file1.txt"), nil}, + {NewPathname("./testdata/file2.txt"), &fs.PathError{}}, + } + + for i, tc := range testcases { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + file, err := tc.p.OpenFile(os.O_RDONLY, 0) + if tc.expectedErr == nil { + assert.NotNil(t, file) + assert.NoError(t, err) + } else { + assert.Nil(t, file) + assert.ErrorAs(t, err, &tc.expectedErr) + } + }) + } +} + +func TestPathname_Size(t *testing.T) { + size, err := NewPathnamePtr("./testdata/file1.txt").Size() + assert.NoError(t, err) + assert.Equal(t, 507, size) +} diff --git a/testdata/file1.txt b/testdata/file1.txt new file mode 100644 index 0000000..6aafba5 --- /dev/null +++ b/testdata/file1.txt @@ -0,0 +1,5 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempus nisi non risus commodo luctus. +Suspendisse laoreet, magna quis aliquet sollicitudin, erat ante euismod purus, ornare molestie ante mauris sed leo. +Sed eu euismod metus, eget interdum dolor. Aliquam pharetra velit ac blandit suscipit. Vivamus dictum massa nibh, +sed sollicitudin velit pulvinar pulvinar. Donec nec ante purus. Pellentesque consequat est sit amet libero +viverra hendrerit. Curabitur condimentum hendrerit mauris ac pretium. \ No newline at end of file