Skip to content

Commit

Permalink
paste implementation (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
mike-ward authored Jul 12, 2024
1 parent df88503 commit fe32f1a
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ count below and mark it as done in this README.md. Thanks!
GNU coreutils. They are not 100% compatiable. If you encounter different behaviors,
compare against the true GNU coreutils version on the Linux-based tests first.

## Completed (65/109)
## Completed (66/109)

| Done | Cmd | Descripton |
| :-----: | --------- | ------------------------------------------------ |
Expand Down Expand Up @@ -108,7 +108,7 @@ compare against the true GNU coreutils version on the Linux-based tests first.
| ✓ | nproc | Print the number of available processors |
| | numfmt | Reformat numbers |
| | od | Write files in octal or other formats |
| | paste | Merge lines of files |
| ✓ | paste | Merge lines of files |
| | pathchk | Check file name validity and portability |
| | pinky | Lightweight finger |
| | pr | Paginate or columnate files for printing |
Expand Down
Empty file removed src/paste/delete.me
Empty file.
87 changes: 87 additions & 0 deletions src/paste/options.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import common
import os
import time

const app_name = 'paste'

struct Options {
serial bool
next_delimiter fn (bool) string = next_delimiter('\t')
zero_terminated bool
files []string
}

fn get_options() Options {
mut fp := common.flag_parser(os.args)
fp.application(app_name)
fp.arguments_description('[FILES]')
fp.description('\nWrite lines consisting of the sequentially corresponding lines from' +
'\neach FILE, separated by TABs, to standard output.' +
'\n\nWith no FILE, or when FILE is -, read standard input.')

delimiters := fp.string('delimiters', `d`, '\t', 'reuse characters from LIST instead of TABs')
serial := fp.bool('serial', `s`, false, 'paste one file at a time instead of in parallel')
zero_terminated := fp.bool('zero-terminated', `z`, false, 'line delimiter is NUL, not newline\n')

files := fp.finalize() or { exit_error(err.msg()) }

return Options{
serial: serial
next_delimiter: next_delimiter(delimiters)
zero_terminated: zero_terminated
files: files
}
}

fn next_delimiter(delimiters string) fn (bool) string {
mut idx := 0
sd := delimiters.runes()
return fn [mut idx, sd] (reset bool) string {
if reset {
idx = 0
return ''
}
delimiter := sd[idx]
idx += (idx + 1) % sd.len
return delimiter.str()
}
}

fn scan_files_arg(files_arg []string) []string {
mut files := []string{}

for file in files_arg {
if file == '-' {
files << stdin_to_tmp()
continue
}
files << file
}

if files.len == 0 {
files << stdin_to_tmp()
}

return files
}

fn stdin_to_tmp() string {
tmp := '${os.temp_dir()}/${app_name}-${time.ticks()}'
os.create(tmp) or { exit_error(err.msg()) }
mut f := os.open_append(tmp) or { exit_error(err.msg()) }
defer { f.close() }

for {
s := os.get_raw_line()
if s.len == 0 {
break
}
f.write_string(s) or { exit_error(err.msg()) }
}
return tmp
}

@[noreturn]
fn exit_error(msg string) {
common.exit_with_error_message(app_name, msg)
}
45 changes: 45 additions & 0 deletions src/paste/paste.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os
import arrays

const max_buf_len = 4096

fn main() {
options := get_options()
paste(options, fn [options] (s string) {
match options.zero_terminated {
true { print(s + '\0') }
else { println(s) }
}
})
}

fn paste(options Options, cb_output fn (string)) {
if options.serial {
for file in options.files {
lines := os.read_lines(file) or { exit_error(err.msg()) }
cb_output(lines.join(options.next_delimiter(false)))
options.next_delimiter(true) // reset
}
return
}

// parallel
mut file_lines := [][]string{}
for file in options.files {
file_lines << os.read_lines(file) or { exit_error(err.msg()) }
}
mut idx := 0
max_lines := arrays.max(file_lines.map(it.len)) or { exit_error(err.msg()) }
for _ in 0 .. max_lines {
mut buf := ''
for i, line in file_lines {
buf += line[idx] or { '' }
if i < file_lines.len - 1 {
buf += options.next_delimiter(false)
}
}
cb_output(buf)
options.next_delimiter(true) // reset
idx += 1
}
}
104 changes: 104 additions & 0 deletions src/paste/paste_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
module main

import os

const file_a = os.temp_dir() + '/paste_file_a.txt'
const file_b = os.temp_dir() + '/paste_file_b.txt'
const file_c = os.temp_dir() + '/paste_file_c.txt'
const file_d = os.temp_dir() + '/paste_file_d.txt'

fn testsuite_begin() {
os.write_lines(file_a, [
'1',
'2',
'3',
'4',
]) or {}
os.write_lines(file_b, [
'a',
'b',
'c',
'd',
]) or {}
os.write_lines(file_c, [
'Now is',
'the time',
'for all',
'good men',
]) or {}
os.write_lines(file_d, [
'to come',
'to the',
'aid of',
'their country',
]) or {}
}

fn add(s string, cb fn (string)) {
cb(s)
}

fn test_serialize_option() {
options := Options{
serial: true
files: [file_a, file_b]
}
mut lines := []string{}
mut rlines := &lines
paste(options, fn [mut rlines] (s string) {
rlines << s
})
assert lines == ['1\t2\t3\t4', 'a\tb\tc\td']
}

fn test_serialize_option2() {
options := Options{
serial: true
files: [file_c, file_d]
}
mut lines := []string{}
mut rlines := &lines
paste(options, fn [mut rlines] (s string) {
rlines << s
})
assert lines == ['Now is\tthe time\tfor all\tgood men', 'to come\tto the\taid of\ttheir country']
}

fn test_single_delimiter() {
options := Options{
files: [file_c, file_d]
}
mut lines := []string{}
mut rlines := &lines
paste(options, fn [mut rlines] (s string) {
rlines << s
})
assert lines == [
// vfmt off
'Now is\tto come',
'the time\tto the',
'for all\taid of',
'good men\ttheir country'
// vfmt on
]
}

fn test_multiple_delimiters() {
options := Options{
next_delimiter: next_delimiter('%|')
files: [file_c, file_d, file_c]
}
mut lines := []string{}
mut rlines := &lines
paste(options, fn [mut rlines] (s string) {
rlines << s
})
assert lines == [
// vfmt off
'Now is%to come|Now is',
'the time%to the|the time',
'for all%aid of|for all',
'good men%their country|good men'
// vfmt on
]
}

0 comments on commit fe32f1a

Please sign in to comment.