csvm2csv: Syntax improvements and error checking

master v3.2.0
Mike Gerwitz 2018-10-02 15:53:44 -04:00
commit d251f7a79b
3 changed files with 314 additions and 11 deletions

View File

@ -20,6 +20,7 @@ SUBDIRS = doc progtest
path_src = src
path_test = test
path_aux = build-aux
# all source files will be run through hoxsl; see `applies' target
apply_src := $(shell find "$(path_src)" "$(path_test)" \
@ -49,6 +50,7 @@ applies: $(apply_dest)
test: check
check: | applies
for test in $(path_aux)/test/test-*; do ./$$test || exit 1; done
$(path_test)/runner
progtest: FORCE

View File

@ -2,7 +2,7 @@
#
# Compiles a "magic" CSV file into a normal CSV
#
# Copyright (C) 2016 R-T Specialty, LLC.
# Copyright (C) 2016, 2018 R-T Specialty, LLC.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -45,6 +45,28 @@
##
# Expand variable with its value, if any
function expand_vars( s, value )
{
# attempt to parse variable (may expand into a range)
if ( match( s, /^\$([a-zA-Z_-]+)$/, m ) )
{
value = vars[ m[1] ];
if ( value == "" )
{
print "error: unknown variable reference: `$" m[1] "'" > "/dev/stderr"
exit 1
}
return value
}
return s
}
# Expand line
function parseline( i, m, j, me, orig )
{
if ( i > NF )
@ -55,6 +77,10 @@ function parseline( i, m, j, me, orig )
orig = $i
# expand variables before any processing so that expansions
# can include any type of formatting
$i = expand_vars( $i )
if ( match( $i, /^([0-9]+\/){2}[0-9]+$/, m ) )
{
cmd = "date --date=" $i " +%s"
@ -78,17 +104,18 @@ function parseline( i, m, j, me, orig )
return
}
# attempt to parse variable (may expand into a range)
if ( match( $i, /^\$([a-zA-Z_-]+)$/, m ) )
{
$i = vars[ m[1] ];
}
# parse range
if ( match( $i, /^([0-9]+)--([0-9]+)$/, m ) )
if ( match( $i, /^([^-]+)--([^-]+)$/, m ) )
{
j = m[1]
me = m[2]
j = expand_vars( m[1] )
me = expand_vars( m[2] )
if ( !match( j, /^[0-9]+$/ ) || !match( me, /^[0-9]+$/ ) )
{
print "error: invalid range: `" $i "'" > "/dev/stderr"
exit 1
}
do
{
$i = j
@ -117,7 +144,12 @@ BEGIN {
# lines that begin with a colon are variable definitions
/^:/ {
match( $0, /^:([a-zA-Z_-]+)=(.*?)$/, m )
if ( !match( $0, /^:([a-zA-Z_-]+)=(.*?)$/, m ) )
{
print "error: invalid variable definition: `" $0 "'" > "/dev/stderr"
exit 1
}
vars[ m[1] ] = m[2]
next
}

View File

@ -0,0 +1,269 @@
#!/bin/bash
# Test csvm2csv
#
# Copyright (C) 2018 R-T Specialty, LLC.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##
cd "$( dirname "$0" )"
# just to ensure that we run all the tests
declare -i testsum=0
# Run test case with input and expected values
run-test()
{
local -r input="${1?Missing input}"
local -r expected="${2?Missing expected}"
((testsum++))
# SUT invocation
declare -r given=$( ../csvm2csv < <( cat <<< "$input" ) )
test $? -eq 0 || return 1
# expected output
diff <( cat <<< "$expected" ) <( cat <<< "$given" )
}
test-comment()
{
local -r input='# comment before header should be removed
header, line
# this is also a comment
1, 2
# which should be ignored
3, 4'
local -r expected='header,line
1,2
3,4'
run-test "$input" "$expected"
}
test-range()
{
declare -r input='header, line
1--3, 2
3--5, 4--6'
declare -r expected='header,line
1,2
2,2
3,2
3,4
3,5
3,6
4,4
4,5
4,6
5,4
5,5
5,6'
run-test "$input" "$expected"
}
test-delim()
{
declare -r input='header, line
1;4, 2
4;3, 6;9'
declare -r expected='header,line
1,2
4,2
4,6
4,9
3,6
3,9'
run-test "$input" "$expected"
}
test-var()
{
declare -r input='header, line
:foo=1
:bar_baz-quux=2
$foo,1
$bar_baz-quux,$foo'
declare -r expected='header,line
1,1
2,1'
run-test "$input" "$expected"
}
test-range-delim()
{
declare -r input='header, line
1--3;5--6, 2'
declare -r expected='header,line
1,2
2,2
3,2
5,2
6,2'
run-test "$input" "$expected"
}
test-var-in-range-delim()
{
declare -r input='header, line
:foo=1
:bar=3
$foo--$bar, $foo;$bar'
declare -r expected='header,line
1,1
1,3
2,1
2,3
3,1
3,3'
run-test "$input" "$expected"
}
test-var-with-range-delim()
{
declare -r input='header, line
:foo=1--2;4
:bar=5
$foo;$bar, 1'
declare -r expected='header,line
1,1
2,1
4,1
5,1'
run-test "$input" "$expected"
}
test-var-with-var()
{
declare -r input='header, line
:foo=2
:bar=4
:range=$foo--$bar
:baz=$range;$foo
$baz, 5'
declare -r expected='header,line
2,5
3,5
4,5
2,5'
run-test "$input" "$expected"
}
# :foo=0 should be considered to be defined
test-var-zero-ref()
{
declare -r input='header, line
:foo=0
$foo'
declare -r expected='header,line
0'
run-test "$input" "$expected"
}
test-fail-unknown-var-ref()
{
((testsum++))
local -r result=$(
../csvm2csv 2>&1 <<< '$undefined' \
&& echo '(test failure: expected failure)'
)
grep -q 'unknown.*\$undefined' <<< "$result" \
|| return 1
}
test-fail-non-numeric-range()
{
((testsum++))
local -r result=$(
../csvm2csv 2>&1 <<< 'A--Z' \
&& echo '(test failure: expected failure)'
)
grep -q 'invalid range.*A--Z' <<< "$result" \
|| return 1
}
test-fail-invalid-var-dfn()
{
((testsum++))
local -r result=$(
../csvm2csv 2>&1 <<< ':BAD@#=var' \
&& echo '(test failure: expected failure)'
)
grep -q 'invalid variable definition.*:BAD@#=var' <<< "$result" \
|| return 1
}
test-comment \
&& test-range \
&& test-delim \
&& test-var \
&& test-range-delim \
&& test-var-in-range-delim \
&& test-var-with-range-delim \
&& test-var-with-var \
&& test-var-zero-ref \
&& test-fail-unknown-var-ref \
&& test-fail-non-numeric-range \
&& test-fail-invalid-var-dfn \
|| {
echo 'csvm2csv test failed' >&2
exit 1
}
# safety check
test "$testsum" -eq 12 || {
echo 'error: did not run all csvm2csv tests!' >&2
exit 1
}