diff --git a/CHANGELOG.md b/CHANGELOG.md index be42304..be7194e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,15 @@ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.11.0] - 2021-11-20 +## [0.12.1] - 2021-11-20 ### Added - base64 command. - cp command. + - nl command. + - -n option for cat command ### Changed - Reduce mimixbox binary size by compile option (7.5MB --> 5.4MB) + - cat/tac command to receive data from standard input when the argument is "-". ## [0.9.1] - 2021-11-19 ### Added - basename command. diff --git a/cmd/mimixbox/main.go b/cmd/mimixbox/main.go index c1ee600..9cd5a1a 100644 --- a/cmd/mimixbox/main.go +++ b/cmd/mimixbox/main.go @@ -50,7 +50,7 @@ type options struct { var osExit = os.Exit -const version = "0.11.0" +const version = "0.12.1" const ( ExitSuccess int = iota // 0 diff --git a/docs/introduction/en/CommandAppletList.md b/docs/introduction/en/CommandAppletList.md index c9b1a5e..f39ca60 100644 --- a/docs/introduction/en/CommandAppletList.md +++ b/docs/introduction/en/CommandAppletList.md @@ -15,6 +15,7 @@ | mkdir | Make directories| | mkfifo | Make FIFO (Named pipe)| | mv | Rename SOURCE to DESTINATION, or move SOURCE(s) to DIRECTORY| +| nl| Write each FILE to standard output with line numbers added| | path | Manipulate filename path| | rm | Remove file(s) or directory(s)| | rmdir | Remove directory| diff --git a/internal/applets/applet.go b/internal/applets/applet.go index db46baa..7e1c828 100644 --- a/internal/applets/applet.go +++ b/internal/applets/applet.go @@ -41,6 +41,7 @@ import ( "mimixbox/internal/applets/shellutils/true" "mimixbox/internal/applets/shellutils/which" "mimixbox/internal/applets/textutils/cat" + "mimixbox/internal/applets/textutils/nl" "mimixbox/internal/applets/textutils/tac" "os" "sort" @@ -72,6 +73,7 @@ func init() { "mkdir": {mkdir.Run, "Make directories"}, "mkfifo": {mkfifo.Run, "Make FIFO (named pipe)"}, "mv": {mv.Run, "Rename SOURCE to DESTINATION, or move SOURCE(s) to DIRECTORY"}, + "nl": {nl.Run, "Write each FILE to standard output with line numbers added"}, "path": {path.Run, "Manipulate filename path"}, "rm": {rm.Run, "Remove file(s) or directory(s)"}, "rmdir": {rmdir.Run, "Remove directory"}, diff --git a/internal/applets/textutils/cat/cat.go b/internal/applets/textutils/cat/cat.go index 6e66c7b..8ebb422 100644 --- a/internal/applets/textutils/cat/cat.go +++ b/internal/applets/textutils/cat/cat.go @@ -18,7 +18,6 @@ package cat import ( "fmt" - "io" mb "mimixbox/internal/lib" "os" @@ -27,7 +26,7 @@ import ( const cmdName string = "cat" -const version = "1.0.2" +const version = "1.0.3" var osExit = os.Exit @@ -38,6 +37,7 @@ const ( ) type options struct { + Number bool `short:"n" long:"number" description:"Print with line number"` Version bool `short:"v" long:"version" description:"Show cat command version"` } @@ -50,34 +50,25 @@ func Run() (int, error) { return ExitFailuer, nil } - if len(args) == 0 { - for { - if !mb.Parrot() { - break - } - } + if len(args) == 0 || mb.Contains(args, "-") { + mb.Parrot(opts.Number) return ExitSuccess, nil } - for _, file := range args { - err := cat(file) - if err != nil { - return ExitFailuer, err - } + strLisr, err := mb.Concatenate(args, false) + if err != nil { + return ExitFailuer, nil } - return ExitSuccess, nil -} - -func cat(path string) error { - f, err := os.Open(path) - if err != nil { - return err + if opts.Number { + mb.PrintStrListWithNumberLine(strLisr, true) + } else { + for _, str := range strLisr { + fmt.Print(str) + } } - defer f.Close() - _, err = io.Copy(os.Stdout, f) - return err + return ExitSuccess, nil } func parseArgs(opts *options) ([]string, error) { diff --git a/internal/applets/textutils/nl/nl.go b/internal/applets/textutils/nl/nl.go new file mode 100644 index 0000000..625afca --- /dev/null +++ b/internal/applets/textutils/nl/nl.go @@ -0,0 +1,105 @@ +// +// mimixbox/internal/applets/textutils/nl/nl.go +// +// Copyright 2021 Naohiro CHIKAMATSU +// +// 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 nl + +import ( + "fmt" + mb "mimixbox/internal/lib" + "os" + + "github.com/jessevdk/go-flags" +) + +const cmdName string = "nl" + +const version = "1.0.0" + +var osExit = os.Exit + +// Exit code +const ( + ExitSuccess int = iota // 0 + ExitFailuer +) + +type options struct { + Version bool `short:"v" long:"version" description:"Show nl command version"` +} + +func Run() (int, error) { + var opts options + var args []string + var err error + + if args, err = parseArgs(&opts); err != nil { + return ExitFailuer, nil + } + + if len(args) == 0 || mb.Contains(args, "-") { + var nr int = 1 + for { + input, next := mb.Input() + if !next { + break + } + if input != "" { + mb.PrintStrWithNumberLine(nr, input+"\n") + nr++ + } else { + fmt.Println("") + } + } + return ExitSuccess, nil + } + + str, err := mb.Concatenate(args, true) + if err != nil { + return ExitFailuer, nil + } + mb.PrintStrListWithNumberLine(str, false) + + return ExitSuccess, nil +} + +func parseArgs(opts *options) ([]string, error) { + p := initParser(opts) + + args, err := p.Parse() + if err != nil { + return nil, err + } + + if opts.Version { + showVersion() + osExit(ExitSuccess) + } + + return args, nil +} + +func initParser(opts *options) *flags.Parser { + parser := flags.NewParser(opts, flags.Default) + parser.Name = cmdName + parser.Usage = "[OPTIONS] FILE_PATH" + + return parser +} + +func showVersion() { + description := cmdName + " version " + version + " (under Apache License verison 2.0)\n" + fmt.Print(description) +} diff --git a/internal/applets/textutils/tac/tac.go b/internal/applets/textutils/tac/tac.go index cf53710..2a63e81 100644 --- a/internal/applets/textutils/tac/tac.go +++ b/internal/applets/textutils/tac/tac.go @@ -27,7 +27,7 @@ import ( const cmdName string = "tac" -const version = "1.0.1" +const version = "1.0.2" var osExit = os.Exit @@ -50,7 +50,7 @@ func Run() (int, error) { return ExitFailuer, nil } - if len(args) == 0 { + if len(args) == 0 || mb.Contains(args, "-") { tacUserInput() return ExitSuccess, nil } diff --git a/internal/lib/file.go b/internal/lib/file.go index 3b409c7..af61afe 100644 --- a/internal/lib/file.go +++ b/internal/lib/file.go @@ -17,6 +17,7 @@ package mb import ( + "bufio" "io" "io/fs" "os" @@ -155,3 +156,21 @@ func Copy(src string, dest string) error { } return nil } + +func ReadFileToStrList(path string) ([]string, error) { + var strList []string + + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + strList = append(strList, scanner.Text()+"\n") + } + + strList[len(strList)-1] = strings.TrimRight(strList[len(strList)-1], "\n") + return strList, nil +} diff --git a/internal/lib/shell.go b/internal/lib/shell.go index f7186c3..1b1f445 100644 --- a/internal/lib/shell.go +++ b/internal/lib/shell.go @@ -65,17 +65,22 @@ func Question(ask string) bool { } } -func Parrot() bool { +func Parrot(withNl bool) { var response string - - _, err := fmt.Scanln(&response) - if err != nil { - if !strings.Contains(err.Error(), "expected newline") { - return false // Ctrl+D or other error. + var nl int = 1 + for { + _, err := fmt.Scanln(&response) + if err != nil { + if !strings.Contains(err.Error(), "expected newline") { + break // Ctrl+D or other error. + } + } + if withNl { + PrintStrWithNumberLine(nl, response) + } else { + fmt.Println(response) } } - fmt.Println(response) - return true } func Input() (string, bool) { @@ -106,3 +111,49 @@ func WrapString(src string, column int) string { } return strings.Join(buf, "\n") } + +func Concatenate(path []string, lfAtTheJoint bool) ([]string, error) { + var strList []string + var index int + + for _, file := range path { + list, err := ReadFileToStrList(file) + if err != nil { + return nil, err + } + + // In the case of the cat command, the beginning of the new file is + // concatenated to the end of the previous file. + // In the case of nl command, do not concatenate the new file at the end + // of the previous file. The end of file (EOF) is replaced with a newline. + index = len(strList) - 1 + if lfAtTheJoint { // for nl command + list[len(list)-1] = list[len(list)-1] + "\n" + strList = append(strList, list...) + } else { // for cat command + if index > 0 { + strList[index] = strList[index] + list[0] + strList = append(strList, list[1:]...) + } else { + strList = append(strList, list...) + } + } + } + return strList, nil +} + +func PrintStrListWithNumberLine(strList []string, countEmpryLine bool) { + var nl int = 1 + for _, s := range strList { + if s == "\n" && !countEmpryLine { + fmt.Print(s) + continue + } + PrintStrWithNumberLine(nl, s) + nl++ + } +} + +func PrintStrWithNumberLine(nl int, str string) { + fmt.Printf("%6d %s", nl, str) +}