diff --git a/Makefile b/Makefile index d254665..d19873f 100644 --- a/Makefile +++ b/Makefile @@ -19,18 +19,18 @@ build-darwin: gen-version ## Build for MacOS -rm -rf ./build/darwin GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \ go build -o ./build/darwin/$(APPROOT) \ - ./cmd/mdfmt/main.go + ./cmd/mdfmt .PHONY: build-linux build-linux: gen-version ## Build for Linux -rm -rf ./build/linux GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \ go build -o ./build/linux/$(APPROOT) \ - ./cmd/mdfmt/main.go + ./cmd/mdfmt .PHONY: build-windows build-windows: gen-version ## Build for Windows -rm -rf ./build/windows GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \ go build -o ./build/windows/$(APPROOT).exe \ - ./cmd/mdfmt/main.go + ./cmd/mdfmt diff --git a/cmd/mdfmt/main.go b/cmd/mdfmt/main.go index b36b0db..53db3ab 100644 --- a/cmd/mdfmt/main.go +++ b/cmd/mdfmt/main.go @@ -51,19 +51,6 @@ var ( `)) ) -type Options struct { - ShowVersion bool - Write bool - Diff bool - List bool - Verbose bool -} - -// NewOptions returns a new Options instance -func NewOptions() *Options { - return &Options{} -} - func configureCLI() *cobra.Command { o := NewOptions() rootCmd := &cobra.Command{ @@ -108,7 +95,7 @@ func configureCLI() *cobra.Command { }() if d != nil && !d.IsDir() && md.IsMarkdownFile(path) { - err2 = md.ProcessMDFile(path, o.Write, o.Diff, o.List) + err2 = ProcessMDFile(path, o.Write, o.Diff, o.List) } return }) diff --git a/cmd/mdfmt/options.go b/cmd/mdfmt/options.go new file mode 100644 index 0000000..b15addc --- /dev/null +++ b/cmd/mdfmt/options.go @@ -0,0 +1,140 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + + "github.com/elliotxx/mdfmt/pkg/md" + "github.com/pkg/diff" + "github.com/pkg/diff/write" +) + +// fdSem guards the number of concurrently-open file descriptors. +// +// For now, this is arbitrarily set to 200, based on the observation that many +// platforms default to a kernel limit of 256. Ideally, perhaps we should derive +// it from rlimit on platforms that support that system call. +// +// File descriptors opened from outside of this package are not tracked, +// so this limit may be approximate. +var fdSem = make(chan bool, 200) + +type Options struct { + ShowVersion bool + Write bool + Diff bool + List bool + Verbose bool +} + +// NewOptions returns a new Options instance +func NewOptions() *Options { + return &Options{} +} + +// ProcessMDFile formats markdown file. +func ProcessMDFile(filePath string, write, diff, list bool) error { + // Get Reader and Source + source, err := os.ReadFile(filePath) + if err != nil { + return err + } + + in := bytes.NewReader(source) + + // Get Writer and Target + out := bytes.NewBuffer([]byte{}) + + err = md.FormatMarkdown(in, out) + if err != nil { + return err + } + + target := out.Bytes() + + if !bytes.Equal(source, target) { + // List + if list { + os.Stdout.WriteString(filePath + "\n") + } + // Write + if write { + info, err := os.Stat(filePath) + if err != nil { + return err + } + + fdSem <- true + + err = os.WriteFile(filePath, target, info.Mode().Perm()) + + <-fdSem + + if err != nil { + return err + } + } + + // Diff + if diff { + data, err := diffWithReplaceTempFile(source, target, filePath) + if err != nil { + return fmt.Errorf("computing diff: %s", err) + } + + fmt.Fprintf(os.Stdout, "diff -u %s %s\n", filepath.ToSlash(filePath+".orig"), filepath.ToSlash(filePath)) + os.Stdout.Write(data) + } + } + + if !write && !diff && !list { + _, err = os.Stdout.Write(target) + } + + return err +} + +func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) { + data := bytes.NewBufferString("") + err := diff.Text("origin", "target", b1, b2, data, write.TerminalColor()) + + if len(data.Bytes()) > 0 { + return replaceTempFilename(data.Bytes(), filename) + } + + return data.Bytes(), err +} + +// replaceTempFilename replaces temporary filenames in diff with actual one. +// +// --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500 +// +++ /tmp/gofmt617882815 2017-02-03 19:13:00.280468375 -0500 +// ... +// -> +// --- path/to/file.md.orig 2017-02-03 19:13:00.280468375 -0500 +// +++ path/to/file.md 2017-02-03 19:13:00.280468375 -0500 +// ... +func replaceTempFilename(diff []byte, filename string) ([]byte, error) { + bs := bytes.SplitN(diff, []byte{'\n'}, 3) + if len(bs) < 3 { + return nil, fmt.Errorf("got unexpected diff for %s", filename) + } + // Preserve timestamps. + var t0, t1 []byte + if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 { + t0 = bs[0][i:] + } + + if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 { + t1 = bs[1][i:] + } + + // Always print filepath with slash separator. + f := filepath.ToSlash(filename) + bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0)) + bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1)) + + return bytes.Join(bs, []byte{'\n'}), nil +} diff --git a/cmd/mdfmt/options_test.go b/cmd/mdfmt/options_test.go new file mode 100644 index 0000000..b61de01 --- /dev/null +++ b/cmd/mdfmt/options_test.go @@ -0,0 +1,84 @@ +package main + +import ( + "os" + "testing" +) + +func TestProcessMDFile(t *testing.T) { + mockMDFile := "./testdata/hello-temp.md" + _ = os.WriteFile(mockMDFile, []byte("# Hello World"), 0644) + + defer os.Remove(mockMDFile) + + type args struct { + filePath string + write bool + diff bool + list bool + } + + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "happy-path", + args: args{ + filePath: "./testdata/hello.md", + write: false, + diff: false, + list: false, + }, + wantErr: false, + }, + { + name: "md-file-not-exist", + args: args{ + filePath: "./testdata/hello-not-exist.md", + write: true, + diff: false, + list: false, + }, + wantErr: true, + }, + { + name: "diff", + args: args{ + filePath: "./testdata/hello.md", + write: false, + diff: true, + list: false, + }, + wantErr: false, + }, + { + name: "write", + args: args{ + filePath: mockMDFile, + write: true, + diff: false, + list: false, + }, + wantErr: false, + }, + { + name: "list", + args: args{ + filePath: "./testdata/hello.md", + write: false, + diff: false, + list: true, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ProcessMDFile(tt.args.filePath, tt.args.write, tt.args.diff, tt.args.list); (err != nil) != tt.wantErr { + t.Errorf("ProcessMDFile() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/md/md.go b/pkg/md/md.go index 16d14ff..6c12f4d 100644 --- a/pkg/md/md.go +++ b/pkg/md/md.go @@ -1,7 +1,6 @@ package md import ( - "bytes" "fmt" "io" "io/ioutil" @@ -12,20 +11,8 @@ import ( "github.com/88250/lute" "github.com/elliotxx/mdfmt/pkg/merrors" - "github.com/pkg/diff" - "github.com/pkg/diff/write" ) -// fdSem guards the number of concurrently-open file descriptors. -// -// For now, this is arbitrarily set to 200, based on the observation that many -// platforms default to a kernel limit of 256. Ideally, perhaps we should derive -// it from rlimit on platforms that support that system call. -// -// File descriptors opened from outside of this package are not tracked, -// so this limit may be approximate. -var fdSem = make(chan bool, 200) - // IsMarkdownFile returns true if the file is a markdown file. func IsMarkdownFile(p string) bool { fi, err := os.Stat(p) @@ -79,108 +66,3 @@ func FormatMarkdown(in io.Reader, out io.Writer) (err error) { return nil } - -// ProcessMDFile formats markdown file. -func ProcessMDFile(filePath string, write, diff, list bool) error { - // Get Reader and Source - source, err := os.ReadFile(filePath) - if err != nil { - return err - } - - in := bytes.NewReader(source) - - // Get Writer and Target - out := bytes.NewBuffer([]byte{}) - - err = FormatMarkdown(in, out) - if err != nil { - return err - } - - target := out.Bytes() - - if !bytes.Equal(source, target) { - // List - if list { - os.Stdout.WriteString(filePath + "\n") - } - // Write - if write { - info, err := os.Stat(filePath) - if err != nil { - return err - } - - fdSem <- true - - err = os.WriteFile(filePath, target, info.Mode().Perm()) - - <-fdSem - - if err != nil { - return err - } - } - - // Diff - if diff { - data, err := diffWithReplaceTempFile(source, target, filePath) - if err != nil { - return fmt.Errorf("computing diff: %s", err) - } - - fmt.Fprintf(os.Stdout, "diff -u %s %s\n", filepath.ToSlash(filePath+".orig"), filepath.ToSlash(filePath)) - os.Stdout.Write(data) - } - } - - if !write && !diff && !list { - _, err = os.Stdout.Write(target) - } - - return err -} - -func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) { - data := bytes.NewBufferString("") - err := diff.Text("origin", "target", b1, b2, data, write.TerminalColor()) - - if len(data.Bytes()) > 0 { - return replaceTempFilename(data.Bytes(), filename) - } - - return data.Bytes(), err -} - -// replaceTempFilename replaces temporary filenames in diff with actual one. -// -// --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500 -// +++ /tmp/gofmt617882815 2017-02-03 19:13:00.280468375 -0500 -// ... -// -> -// --- path/to/file.md.orig 2017-02-03 19:13:00.280468375 -0500 -// +++ path/to/file.md 2017-02-03 19:13:00.280468375 -0500 -// ... -func replaceTempFilename(diff []byte, filename string) ([]byte, error) { - bs := bytes.SplitN(diff, []byte{'\n'}, 3) - if len(bs) < 3 { - return nil, fmt.Errorf("got unexpected diff for %s", filename) - } - // Preserve timestamps. - var t0, t1 []byte - if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 { - t0 = bs[0][i:] - } - - if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 { - t1 = bs[1][i:] - } - - // Always print filepath with slash separator. - f := filepath.ToSlash(filename) - bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0)) - bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1)) - - return bytes.Join(bs, []byte{'\n'}), nil -} diff --git a/pkg/md/md_test.go b/pkg/md/md_test.go index d10082c..84e266b 100644 --- a/pkg/md/md_test.go +++ b/pkg/md/md_test.go @@ -3,7 +3,6 @@ package md import ( "bytes" "io" - "os" "strings" "testing" ) @@ -104,81 +103,3 @@ func TestFormatMarkdown(t *testing.T) { }) } } - -func TestProcessMDFile(t *testing.T) { - mockMDFile := "./testdata/hello-temp.md" - _ = os.WriteFile(mockMDFile, []byte("# Hello World"), 0644) - - defer os.Remove(mockMDFile) - - type args struct { - filePath string - write bool - diff bool - list bool - } - - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "happy-path", - args: args{ - filePath: "./testdata/hello.md", - write: false, - diff: false, - list: false, - }, - wantErr: false, - }, - { - name: "md-file-not-exist", - args: args{ - filePath: "./testdata/hello-not-exist.md", - write: true, - diff: false, - list: false, - }, - wantErr: true, - }, - { - name: "diff", - args: args{ - filePath: "./testdata/hello.md", - write: false, - diff: true, - list: false, - }, - wantErr: false, - }, - { - name: "write", - args: args{ - filePath: mockMDFile, - write: true, - diff: false, - list: false, - }, - wantErr: false, - }, - { - name: "list", - args: args{ - filePath: "./testdata/hello.md", - write: false, - diff: false, - list: true, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := ProcessMDFile(tt.args.filePath, tt.args.write, tt.args.diff, tt.args.list); (err != nil) != tt.wantErr { - t.Errorf("ProcessMDFile() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -}