17343101 苏祺达
由于对课程作业目录进行了一次重新组织,将../../projects/文件夹改为 Go 工作空间,仅存放代码而不存放报告,因此本次实验源代码请戳这里,带来的改作业上的不便,敬请谅解。
由于原 selpg 项目中测试时使用的默认将每72行视为一页的做法会带来对测试结果展示上的不便,因此将部分测试命令加上-l4选项,即将每4行视为一页。要对该选项的默认值进行测试,请下载源代码到本地上进行测试。
测试时使用的测试文件放在../../testdatas/operation4/下,测试命令中与之相关的路径部分将会使用环境变量代替。
selpg -s1 -e1 -l4 $INPUT_FILE
测试描述:
该命令将把“$INPUT_FILE”的第 1 页写至标准输出(也就是屏幕),因为这里有 重 定向或管道。
测试结果:

selpg -s1 -e1 -l4 < $INPUT_FILE
测试描述:
该命令与测试 1 所做的工作相同,但在本测试中,selpg 读取标准输入,而标准输入已被 shell/内核重定向为来自“input_file”而不是显式命名的文件名参数。输入的第 1 页被写至屏幕。
测试结果:

cat $INPUT_FILE | selpg -s1 -e2 -l4
测试描述:
“cat $INPUT_FILE”的标准输出被 shell/内核重定向至 selpg 的标准输入。将第 1 页到第 2 页写至 selpg 的标准输出(屏幕)。
测试结果:

selpg -s1 -e2 -l4 $INPUT_FILE >$OUTPUT_FILE
测试描述:
selpg 将第 1 页到第 2 页写至标准输出;标准输出被 shell/内核重定向至“$OUTPUT_FILE”。
测试结果:
……(后面太长不想测)
貌似没有在flag或者pflag里头找到模式匹配参数的用法?于是自己造轮子。
首先规定一个参数匹配器结构,使用 Pattern 检查参数流第一个参数,如果匹配则使用 Next 消耗参数流并执行附加操作。使用指针是否为 nil 判断该参数是否输入。
type flagMatcher struct {
Pattern string
Next func(*[]string) error
Usage string
}
var startPageN, endPageN, linesPerPage *int
var formFeed, showHelpMsg *bool
var toDestination, filename *string
使用默认的校验器消耗所有未匹配的参数,将这些参数视为输入文件名:
&flagMatcher{
Pattern: ".*",
Next: func(flagstream *[]string) error {
filename = &(*flagstream)[0]
*flagstream = (*flagstream)[1:]
return nil
},
Usage: "[filename]\tSpecify the input file",
},
丢弃第一个参数,因为第一个参数是程序名:
args := os.Args[1:]
if err := parseFlags(&args); err != nil {
os.Stderr.Write([]byte(err.Error() + "\n"))
return
}
不断地检查并使用参数,直至参数流被消耗完毕:
for len(*flagstream) > 0 {
for _, matcher := range matchers {
if matched, err := regexp.MatchString(matcher.Pattern, (*flagstream)[0]); err == nil && matched {
if err = matcher.Next(flagstream); err != nil {
return err
} else {
break
}
}
}
}
最后对输入的参数进行合法性检查,检查是否有互斥的参数设置,是否有必选的参数缺省,并对有默认值的参数进行补全。若使用-h参数显示帮助信息,则跳过参数检查,因为其余参数将不会被用到。
initValidatiors()
for _, validator := range validators {
if validator.Condition {
return errors.New(validator.ErrorMsg)
}
}
if formFeed == nil && linesPerPage == nil {
temp1 := 72
linesPerPage = &temp1
temp2 := false
formFeed = &temp2
}
if showHelpMsg == nil {
temp := false
showHelpMsg = &temp
}
如果参数的文件名不为空,则打开对应的文件,否则使用os.Stdin作为输入的reader。
如果-d参数已经设置,则创建子进程,并且获得其StdinPipe,否则使用os.Stdout作为输出的writer。
if filename != nil {
if input, err = os.Open(*filename); err != nil {
return err
}
} else {
input = os.Stdin
}
if toDestination != nil {
if output, err = exec.Command("lp", "-d"+*toDestination).StdinPipe(); err != nil {
return err
}
} else {
output = os.Stdout
}
使用一个缓冲结构缓冲输入和输出的内容:
type byteBuf struct {
byteBuf []byte
ptr int
size int
}
每次读取字符前,判断输入缓冲是否为空,如果为空,则从输入reader中读取和缓冲区相同大小的字节数。程序结束前,判断输出缓冲是否不为空,如果不为空,则依据情况将输出缓冲的内容清空或者输出到输出writer中。
func readUntil(delimiter byte, shouldWrite bool) error {
for {
if readBuf.IsEmpty() && !readBuf.Read(input) {
break
}
nextByte := readBuf.ReadNextByte()
if nextByte == delimiter {
break
}
writeBuf.WriteNextByte(nextByte)
if writeBuf.IsFull() {
if shouldWrite {
if err := writeBuf.Write(output); err != nil {
return err
}
} else {
writeBuf.Clear()
}
}
}
if !writeBuf.IsEmpty() {
if shouldWrite {
if err := writeBuf.Write(output); err != nil {
return err
}
} else {
writeBuf.Clear()
}
}
return nil
}