Merge pull request #38 from stephen679/vis

vis: refactor code to use Vis() and Unvis()
This commit is contained in:
Vincent Batts 2016-07-25 17:59:48 -04:00 committed by GitHub
commit c74c2ed6d7
15 changed files with 810 additions and 25 deletions

View file

@ -53,8 +53,11 @@ func Check(root string, dh *DirectoryHierarchy, keywords []string) (*Result, err
creator.curSet = nil creator.curSet = nil
} }
case RelativeType, FullType: case RelativeType, FullType:
filename := e.Path() pathname, err := e.Path()
info, err := os.Lstat(filename) if err != nil {
return nil, err
}
info, err := os.Lstat(pathname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -77,23 +80,23 @@ func Check(root string, dh *DirectoryHierarchy, keywords []string) (*Result, err
keywordFunc, ok := KeywordFuncs[kw] keywordFunc, ok := KeywordFuncs[kw]
if !ok { if !ok {
return nil, fmt.Errorf("Unknown keyword %q for file %q", kv.Keyword(), e.Path()) return nil, fmt.Errorf("Unknown keyword %q for file %q", kv.Keyword(), pathname)
} }
if keywords != nil && !inSlice(kv.Keyword(), keywords) { if keywords != nil && !inSlice(kv.Keyword(), keywords) {
continue continue
} }
fh, err := os.Open(filename) fh, err := os.Open(pathname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
curKeyVal, err := keywordFunc(filename, info, fh) curKeyVal, err := keywordFunc(pathname, info, fh)
if err != nil { if err != nil {
fh.Close() fh.Close()
return nil, err return nil, err
} }
fh.Close() fh.Close()
if string(kv) != curKeyVal { if string(kv) != curKeyVal {
failure := Failure{Path: e.Path(), Keyword: kv.Keyword(), Expected: kv.Value(), Got: KeyVal(curKeyVal).Value()} failure := Failure{Path: pathname, Keyword: kv.Keyword(), Expected: kv.Value(), Got: KeyVal(curKeyVal).Value()}
result.Failures = append(result.Failures, failure) result.Failures = append(result.Failures, failure)
} }
} }
@ -133,8 +136,12 @@ func TarCheck(tarDH, dh *DirectoryHierarchy, keywords []string) (*Result, error)
creator.curSet = nil creator.curSet = nil
} }
case RelativeType, FullType: case RelativeType, FullType:
pathname, err := e.Path()
if err != nil {
return nil, err
}
if outOfTree { if outOfTree {
return &result, fmt.Errorf("No parent node from %s", e.Path()) return &result, fmt.Errorf("No parent node from %s", pathname)
} }
// TODO: handle the case where "." is not the first Entry to be found // TODO: handle the case where "." is not the first Entry to be found
tarEntry := curDir.Descend(e.Name) tarEntry := curDir.Descend(e.Name)
@ -165,15 +172,20 @@ func TarCheck(tarDH, dh *DirectoryHierarchy, keywords []string) (*Result, error)
} }
for _, kv := range kvs { for _, kv := range kvs {
if _, ok := KeywordFuncs[kv.Keyword()]; !ok { if _, ok := KeywordFuncs[kv.Keyword()]; !ok {
return nil, fmt.Errorf("Unknown keyword %q for file %q", kv.Keyword(), e.Path()) return nil, fmt.Errorf("Unknown keyword %q for file %q", kv.Keyword(), pathname)
} }
if keywords != nil && !inSlice(kv.Keyword(), keywords) { if keywords != nil && !inSlice(kv.Keyword(), keywords) {
continue continue
} }
tarpath, err := tarEntry.Path()
if err != nil {
return nil, err
}
if tarkv := tarkvs.Has(kv.Keyword()); tarkv != emptyKV { if tarkv := tarkvs.Has(kv.Keyword()); tarkv != emptyKV {
if string(tarkv) != string(kv) { if string(tarkv) != string(kv) {
failure := Failure{Path: tarEntry.Path(), Keyword: kv.Keyword(), Expected: kv.Value(), Got: tarkv.Value()} failure := Failure{Path: tarpath, Keyword: kv.Keyword(), Expected: kv.Value(), Got: tarkv.Value()}
result.Failures = append(result.Failures, failure) result.Failures = append(result.Failures, failure)
} }
} }

View file

@ -260,3 +260,38 @@ func TestIgnoreComments(t *testing.T) {
t.Fatal(res.Failures) t.Fatal(res.Failures)
} }
} }
func TestCheckNeedsEncoding(t *testing.T) {
dir, err := ioutil.TempDir("", "test-needs-encoding")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
fh, err := os.Create(filepath.Join(dir, "file[ "))
if err != nil {
t.Fatal(err)
}
if err := fh.Close(); err != nil {
t.Error(err)
}
fh, err = os.Create(filepath.Join(dir, " , should work"))
if err != nil {
t.Fatal(err)
}
if err := fh.Close(); err != nil {
t.Error(err)
}
dh, err := Walk(dir, nil, DefaultKeywords)
if err != nil {
t.Fatal(err)
}
res, err := Check(dir, dh, nil)
if err != nil {
t.Fatal(err)
}
if len(res.Failures) > 0 {
t.Fatal(res.Failures)
}
}

View file

@ -202,13 +202,25 @@ func main() {
if len(res.Extra) > 0 { if len(res.Extra) > 0 {
defer os.Exit(1) defer os.Exit(1)
for _, extra := range res.Extra { for _, extra := range res.Extra {
fmt.Printf("%s extra\n", extra.Path()) extrapath, err := extra.Path()
if err != nil {
log.Println(err)
isErr = true
return
}
fmt.Printf("%s extra\n", extrapath)
} }
} }
if len(res.Missing) > 0 { if len(res.Missing) > 0 {
defer os.Exit(1) defer os.Exit(1)
for _, missing := range res.Missing { for _, missing := range res.Missing {
fmt.Printf("%s missing\n", missing.Path()) missingpath, err := missing.Path()
if err != nil {
log.Println(err)
isErr = true
return
}
fmt.Printf("%s missing\n", missingpath)
} }
} }
} else { } else {

View file

@ -48,14 +48,25 @@ func (e Entry) Ascend() *Entry {
return e.Parent return e.Parent
} }
// Path provides the full path of the file, despite RelativeType or FullType // Path provides the full path of the file, despite RelativeType or FullType. It
func (e Entry) Path() string { // will be in Unvis'd form.
if e.Parent == nil || e.Type == FullType { func (e Entry) Path() (string, error) {
return filepath.Clean(e.Name) decodedName, err := Unvis(e.Name)
if err != nil {
return "", err
} }
return filepath.Clean(filepath.Join(e.Parent.Path(), e.Name)) if e.Parent == nil || e.Type == FullType {
return filepath.Clean(decodedName), nil
}
parentName, err := e.Parent.Path()
if err != nil {
return "", err
}
return filepath.Clean(filepath.Join(parentName, decodedName)), nil
} }
// String joins a file with its associated keywords. The file name will be the
// Vis'd encoded version so that it can be parsed appropriately when Check'd.
func (e Entry) String() string { func (e Entry) String() string {
if e.Raw != "" { if e.Raw != "" {
return e.Raw return e.Raw

View file

@ -16,7 +16,8 @@ func (dh DirectoryHierarchy) WriteTo(w io.Writer) (n int64, err error) {
sort.Sort(byPos(dh.Entries)) sort.Sort(byPos(dh.Entries))
var sum int64 var sum int64
for _, e := range dh.Entries { for _, e := range dh.Entries {
i, err := io.WriteString(w, e.String()+"\n") str := e.String()
i, err := io.WriteString(w, str+"\n")
if err != nil { if err != nil {
return sum, err return sum, err
} }

23
tar.go
View file

@ -97,8 +97,15 @@ func (ts *tarStream) readHeaders() {
defer os.Remove(tmpFile.Name()) defer os.Remove(tmpFile.Name())
// Alright, it's either file or directory // Alright, it's either file or directory
encodedName, err := Vis(filepath.Base(hdr.Name))
if err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
e := Entry{ e := Entry{
Name: filepath.Base(hdr.Name), Name: encodedName,
Type: RelativeType, Type: RelativeType,
} }
@ -213,8 +220,13 @@ func populateTree(root, e *Entry, hdr *tar.Header, ts *tarStream) {
if isDir { if isDir {
newEntry = e newEntry = e
} else { } else {
encodedName, err := Vis(name)
if err != nil {
ts.setErr(err)
return
}
newEntry = &Entry{ newEntry = &Entry{
Name: name, Name: encodedName,
Type: RelativeType, Type: RelativeType,
} }
} }
@ -230,8 +242,13 @@ func populateTree(root, e *Entry, hdr *tar.Header, ts *tarStream) {
parent.Children = append([]*Entry{e}, parent.Children...) parent.Children = append([]*Entry{e}, parent.Children...)
e.Parent = parent e.Parent = parent
} else { } else {
commentpath, err := e.Path()
if err != nil {
ts.setErr(err)
return
}
commentEntry := Entry{ commentEntry := Entry{
Raw: "# " + e.Path(), Raw: "# " + commentpath,
Type: CommentType, Type: CommentType,
} }
e.Prev = &commentEntry e.Prev = &commentEntry

View file

@ -119,12 +119,20 @@ func TestTar(t *testing.T) {
errors += "Keyword validation errors\n" errors += "Keyword validation errors\n"
case len(res.Missing) > 0: case len(res.Missing) > 0:
for _, m := range res.Missing { for _, m := range res.Missing {
t.Errorf("Missing file: %s\n", m.Path()) missingpath, err := m.Path()
if err != nil {
t.Fatal(err)
}
t.Errorf("Missing file: %s\n", missingpath)
} }
errors += "Missing files not expected for this test\n" errors += "Missing files not expected for this test\n"
case len(res.Extra) > 0: case len(res.Extra) > 0:
for _, e := range res.Extra { for _, e := range res.Extra {
t.Errorf("Extra file: %s\n", e.Path()) extrapath, err := e.Path()
if err != nil {
t.Fatal(err)
}
t.Errorf("Extra file: %s\n", extrapath)
} }
errors += "Extra files not expected for this test\n" errors += "Extra files not expected for this test\n"
} }

BIN
testdata/test.tar vendored

Binary file not shown.

293
unvis.c Normal file
View file

@ -0,0 +1,293 @@
/*-
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)unvis.c 8.1 (Berkeley) 6/4/93";
#endif /* LIBC_SCCS and not lint */
#include <sys/cdefs.h>
#include <sys/types.h>
#include <ctype.h>
#include "vis.h"
/*
* decode driven by state machine
*/
#define S_GROUND 0 /* haven't seen escape char */
#define S_START 1 /* start decoding special sequence */
#define S_META 2 /* metachar started (M) */
#define S_META1 3 /* metachar more, regular char (-) */
#define S_CTRL 4 /* control char started (^) */
#define S_OCTAL2 5 /* octal digit 2 */
#define S_OCTAL3 6 /* octal digit 3 */
#define S_HEX2 7 /* hex digit 2 */
#define S_HTTP 0x080 /* %HEXHEX escape */
#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
#define ishex(c) ((((u_char)(c)) >= '0' && ((u_char)(c)) <= '9') || (((u_char)(c)) >= 'a' && ((u_char)(c)) <= 'f'))
/*
* unvis - decode characters previously encoded by vis
*/
int
unvis(char *cp, int c, int *astate, int flag)
{
if (flag & UNVIS_END) {
if (*astate == S_OCTAL2 || *astate == S_OCTAL3) {
*astate = S_GROUND;
return (UNVIS_VALID);
}
return (*astate == S_GROUND ? UNVIS_NOCHAR : UNVIS_SYNBAD);
}
switch (*astate & ~S_HTTP) {
case S_GROUND:
*cp = 0;
if (c == '\\') {
*astate = S_START;
return (0);
}
if (flag & VIS_HTTPSTYLE && c == '%') {
*astate = S_START | S_HTTP;
return (0);
}
*cp = c;
return (UNVIS_VALID);
case S_START:
if (*astate & S_HTTP) {
if (ishex(tolower(c))) {
*cp = isdigit(c) ? (c - '0') : (tolower(c) - 'a');
*astate = S_HEX2;
return (0);
}
}
switch(c) {
case '\\':
*cp = c;
*astate = S_GROUND;
return (UNVIS_VALID);
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
*cp = (c - '0');
*astate = S_OCTAL2;
return (0);
case 'M':
*cp = 0200;
*astate = S_META;
return (0);
case '^':
*astate = S_CTRL;
return (0);
case 'n':
*cp = '\n';
*astate = S_GROUND;
return (UNVIS_VALID);
case 'r':
*cp = '\r';
*astate = S_GROUND;
return (UNVIS_VALID);
case 'b':
*cp = '\b';
*astate = S_GROUND;
return (UNVIS_VALID);
case 'a':
*cp = '\007';
*astate = S_GROUND;
return (UNVIS_VALID);
case 'v':
*cp = '\v';
*astate = S_GROUND;
return (UNVIS_VALID);
case 't':
*cp = '\t';
*astate = S_GROUND;
return (UNVIS_VALID);
case 'f':
*cp = '\f';
*astate = S_GROUND;
return (UNVIS_VALID);
case 's':
*cp = ' ';
*astate = S_GROUND;
return (UNVIS_VALID);
case 'E':
*cp = '\033';
*astate = S_GROUND;
return (UNVIS_VALID);
case '\n':
/*
* hidden newline
*/
*astate = S_GROUND;
return (UNVIS_NOCHAR);
case '$':
/*
* hidden marker
*/
*astate = S_GROUND;
return (UNVIS_NOCHAR);
}
*astate = S_GROUND;
return (UNVIS_SYNBAD);
case S_META:
if (c == '-')
*astate = S_META1;
else if (c == '^')
*astate = S_CTRL;
else {
*astate = S_GROUND;
return (UNVIS_SYNBAD);
}
return (0);
case S_META1:
*astate = S_GROUND;
*cp |= c;
return (UNVIS_VALID);
case S_CTRL:
if (c == '?')
*cp |= 0177;
else
*cp |= c & 037;
*astate = S_GROUND;
return (UNVIS_VALID);
case S_OCTAL2: /* second possible octal digit */
if (isoctal(c)) {
/*
* yes - and maybe a third
*/
*cp = (*cp << 3) + (c - '0');
*astate = S_OCTAL3;
return (0);
}
/*
* no - done with current sequence, push back passed char
*/
*astate = S_GROUND;
return (UNVIS_VALIDPUSH);
case S_OCTAL3: /* third possible octal digit */
*astate = S_GROUND;
if (isoctal(c)) {
*cp = (*cp << 3) + (c - '0');
return (UNVIS_VALID);
}
/*
* we were done, push back passed char
*/
return (UNVIS_VALIDPUSH);
case S_HEX2: /* second mandatory hex digit */
if (ishex(tolower(c))) {
*cp = (isdigit(c) ? (*cp << 4) + (c - '0') : (*cp << 4) + (tolower(c) - 'a' + 10));
}
*astate = S_GROUND;
return (UNVIS_VALID);
default:
/*
* decoder in unknown state - (probably uninitialized)
*/
*astate = S_GROUND;
return (UNVIS_SYNBAD);
}
}
/*
* strunvis - decode src into dst
*
* Number of chars decoded into dst is returned, -1 on error.
* Dst is null terminated.
*/
int
strunvis(char *dst, const char *src)
{
char c;
char *start = dst;
int state = 0;
while ( (c = *src++) ) {
again:
switch (unvis(dst, c, &state, 0)) {
case UNVIS_VALID:
dst++;
break;
case UNVIS_VALIDPUSH:
dst++;
goto again;
case 0:
case UNVIS_NOCHAR:
break;
default:
return (-1);
}
}
if (unvis(dst, c, &state, UNVIS_END) == UNVIS_VALID)
dst++;
*dst = '\0';
return (dst - start);
}
int
strunvisx(char *dst, const char *src, int flag)
{
char c;
char *start = dst;
int state = 0;
while ( (c = *src++) ) {
again:
switch (unvis(dst, c, &state, flag)) {
case UNVIS_VALID:
dst++;
break;
case UNVIS_VALIDPUSH:
dst++;
goto again;
case 0:
case UNVIS_NOCHAR:
break;
default:
return (-1);
}
}
if (unvis(dst, c, &state, UNVIS_END) == UNVIS_VALID)
dst++;
*dst = '\0';
return (dst - start);
}

22
unvis.go Normal file
View file

@ -0,0 +1,22 @@
package mtree
// #include "vis.h"
// #include <stdlib.h>
import "C"
import (
"fmt"
"unsafe"
)
// Unvis is a wrapper for the C implementation of unvis, which decodes a string
// that potentially has characters that are encoded with Vis
func Unvis(src string) (string, error) {
cDst, cSrc := C.CString(string(make([]byte, len(src)+1))), C.CString(src)
defer C.free(unsafe.Pointer(cDst))
defer C.free(unsafe.Pointer(cSrc))
ret := C.strunvis(cDst, cSrc)
if ret == -1 {
return "", fmt.Errorf("failed to decode: %q", src)
}
return C.GoString(cDst), nil
}

202
vis.c Normal file
View file

@ -0,0 +1,202 @@
/*-
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)vis.c 8.1 (Berkeley) 7/19/93";
#endif /* LIBC_SCCS and not lint */
#include <sys/cdefs.h>
#include <sys/types.h>
#include <limits.h>
#include <ctype.h>
#include <stdio.h>
#include "vis.h"
#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
/*
* vis - visually encode characters
*/
char *
vis(dst, c, flag, nextc)
char *dst;
int c, nextc;
int flag;
{
c = (unsigned char)c;
if (flag & VIS_HTTPSTYLE) {
/* Described in RFC 1808 */
if (!(isalnum(c) /* alpha-numeric */
/* safe */
|| c == '$' || c == '-' || c == '_' || c == '.' || c == '+'
/* extra */
|| c == '!' || c == '*' || c == '\'' || c == '('
|| c == ')' || c == ',')) {
*dst++ = '%';
snprintf(dst, 4, (c < 16 ? "0%X" : "%X"), c);
dst += 2;
goto done;
}
}
if ((flag & VIS_GLOB) &&
(c == '*' || c == '?' || c == '[' || c == '#'))
;
else if (isgraph(c) ||
((flag & VIS_SP) == 0 && c == ' ') ||
((flag & VIS_TAB) == 0 && c == '\t') ||
((flag & VIS_NL) == 0 && c == '\n') ||
((flag & VIS_SAFE) && (c == '\b' || c == '\007' || c == '\r'))) {
*dst++ = c;
if (c == '\\' && (flag & VIS_NOSLASH) == 0)
*dst++ = '\\';
*dst = '\0';
return (dst);
}
if (flag & VIS_CSTYLE) {
switch(c) {
case '\n':
*dst++ = '\\';
*dst++ = 'n';
goto done;
case '\r':
*dst++ = '\\';
*dst++ = 'r';
goto done;
case '\b':
*dst++ = '\\';
*dst++ = 'b';
goto done;
case '\a':
*dst++ = '\\';
*dst++ = 'a';
goto done;
case '\v':
*dst++ = '\\';
*dst++ = 'v';
goto done;
case '\t':
*dst++ = '\\';
*dst++ = 't';
goto done;
case '\f':
*dst++ = '\\';
*dst++ = 'f';
goto done;
case ' ':
*dst++ = '\\';
*dst++ = 's';
goto done;
case '\0':
*dst++ = '\\';
*dst++ = '0';
if (isoctal(nextc)) {
*dst++ = '0';
*dst++ = '0';
}
goto done;
}
}
if (((c & 0177) == ' ') || isgraph(c) || (flag & VIS_OCTAL)) {
*dst++ = '\\';
*dst++ = ((u_char)c >> 6 & 07) + '0';
*dst++ = ((u_char)c >> 3 & 07) + '0';
*dst++ = ((u_char)c & 07) + '0';
goto done;
}
if ((flag & VIS_NOSLASH) == 0)
*dst++ = '\\';
if (c & 0200) {
c &= 0177;
*dst++ = 'M';
}
if (iscntrl(c)) {
*dst++ = '^';
if (c == 0177)
*dst++ = '?';
else
*dst++ = c + '@';
} else {
*dst++ = '-';
*dst++ = c;
}
done:
*dst = '\0';
return (dst);
}
/*
* strvis, strvisx - visually encode characters from src into dst
*
* Dst must be 4 times the size of src to account for possible
* expansion. The length of dst, not including the trailing NUL,
* is returned.
*
* Strvisx encodes exactly len bytes from src into dst.
* This is useful for encoding a block of data.
*/
int
strvis(dst, src, flag)
char *dst;
const char *src;
int flag;
{
char c;
char *start;
for (start = dst; (c = *src); )
dst = vis(dst, c, flag, *++src);
*dst = '\0';
return (dst - start);
}
int
strvisx(dst, src, len, flag)
char *dst;
const char *src;
size_t len;
int flag;
{
int c;
char *start;
for (start = dst; len > 1; len--) {
c = *src;
dst = vis(dst, c, flag, *++src);
}
if (len)
dst = vis(dst, *src, flag, '\0');
*dst = '\0';
return (dst - start);
}

26
vis.go Normal file
View file

@ -0,0 +1,26 @@
package mtree
// #include "vis.h"
// #include <stdlib.h>
import "C"
import (
"fmt"
"math"
"unsafe"
)
// Vis is a wrapper of the C implementation of the function vis, which encodes
// a character with a particular format/style
func Vis(src string) (string, error) {
// dst needs to be 4 times the length of str, must check appropriate size
if uint32(len(src)*4+1) >= math.MaxUint32/4 {
return "", fmt.Errorf("failed to encode: %q", src)
}
dst := string(make([]byte, 4*len(src)+1))
cDst, cSrc := C.CString(dst), C.CString(src)
defer C.free(unsafe.Pointer(cDst))
defer C.free(unsafe.Pointer(cSrc))
C.strvis(cDst, cSrc, C.VIS_WHITE|C.VIS_OCTAL|C.VIS_GLOB)
return C.GoString(cDst), nil
}

90
vis.h Normal file
View file

@ -0,0 +1,90 @@
/*-
* Copyright (c) 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)vis.h 8.1 (Berkeley) 6/2/93
* $FreeBSD$
*/
#ifndef _VIS_H_
#define _VIS_H_
#include <sys/types.h>
/*
* to select alternate encoding format
*/
#define VIS_OCTAL 0x01 /* use octal \ddd format */
#define VIS_CSTYLE 0x02 /* use \[nrft0..] where appropriate */
/*
* to alter set of characters encoded (default is to encode all
* non-graphic except space, tab, and newline).
*/
#define VIS_SP 0x04 /* also encode space */
#define VIS_TAB 0x08 /* also encode tab */
#define VIS_NL 0x10 /* also encode newline */
#define VIS_WHITE (VIS_SP | VIS_TAB | VIS_NL)
#define VIS_SAFE 0x20 /* only encode "unsafe" characters */
/*
* other
*/
#define VIS_NOSLASH 0x40 /* inhibit printing '\' */
#define VIS_HTTPSTYLE 0x80 /* http-style escape % HEX HEX */
#define VIS_GLOB 0x100 /* encode glob(3) magics */
/*
* unvis return codes
*/
#define UNVIS_VALID 1 /* character valid */
#define UNVIS_VALIDPUSH 2 /* character valid, push back passed char */
#define UNVIS_NOCHAR 3 /* valid sequence, no character produced */
#define UNVIS_SYNBAD -1 /* unrecognized escape sequence */
#define UNVIS_ERROR -2 /* decoder in unknown state (unrecoverable) */
/*
* unvis flags
*/
#define UNVIS_END 1 /* no more characters */
#include <sys/cdefs.h>
__BEGIN_DECLS
char *vis(char *, int, int, int);
int strvis(char *, const char *, int);
int strvisx(char *, const char *, size_t, int);
int strunvis(char *, const char *);
int strunvisx(char *, const char *, int);
int unvis(char *, int, int *, int);
__END_DECLS
#endif /* !_VIS_H_ */

49
vis_test.go Normal file
View file

@ -0,0 +1,49 @@
package mtree
import "testing"
func TestVis(t *testing.T) {
testset := []struct {
Src, Dest string
}{
{"[", "\\133"},
{" ", "\\040"},
{" ", "\\011"},
{"dir with space", "dir\\040with\\040space"},
{"consec spaces", "consec\\040\\040\\040spaces"},
{"trailingsymbol[", "trailingsymbol\\133"},
{" [ leadingsymbols", "\\040\\133\\040leadingsymbols"},
{"no_need_for_encoding", "no_need_for_encoding"},
}
for i := range testset {
got, err := Vis(testset[i].Src)
if err != nil {
t.Errorf("working with %q: %s", testset[i].Src, err)
}
if got != testset[i].Dest {
t.Errorf("expected %#v; got %#v", testset[i].Dest, got)
continue
}
got, err = Unvis(got)
if err != nil {
t.Errorf("working with %q: %s", testset[i].Src, err)
continue
}
if got != testset[i].Src {
t.Errorf("expected %#v; got %#v", testset[i].Src, got)
continue
}
}
}
// The resulting string of Vis output could potentially be four times longer than
// the original. Vis must handle this possibility.
func TestVisLength(t *testing.T) {
testString := "All work and no play makes Jack a dull boy\n"
for i := 0; i < 20; i++ {
Vis(testString)
testString = testString + testString
}
}

13
walk.go
View file

@ -47,9 +47,13 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie
// Insert a comment of the full path of the directory's name // Insert a comment of the full path of the directory's name
if creator.curDir != nil { if creator.curDir != nil {
dirname, err := creator.curDir.Path()
if err != nil {
return err
}
creator.DH.Entries = append(creator.DH.Entries, Entry{ creator.DH.Entries = append(creator.DH.Entries, Entry{
Pos: len(creator.DH.Entries), Pos: len(creator.DH.Entries),
Raw: "# " + filepath.Join(creator.curDir.Path(), entryPathName), Raw: "# " + filepath.Join(dirname, entryPathName),
Type: CommentType, Type: CommentType,
}) })
} else { } else {
@ -147,9 +151,12 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie
} }
} }
} }
encodedEntryName, err := Vis(entryPathName)
if err != nil {
return err
}
e := Entry{ e := Entry{
Name: entryPathName, Name: encodedEntryName,
Pos: len(creator.DH.Entries), Pos: len(creator.DH.Entries),
Type: RelativeType, Type: RelativeType,
Set: creator.curSet, Set: creator.curSet,