Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserving the backslash character #37

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -4,3 +4,4 @@
*.swp
.idea/
*.iml
.vscode/
2 changes: 1 addition & 1 deletion decode_test.go
Expand Up @@ -278,7 +278,7 @@ func TestDecodeMap(t *testing.T) {
}

func testDecode(t *testing.T, in string, v, out interface{}) {
p, err := parse(in)
p, err := parse(in, false)
if err != nil {
t.Fatalf("got %v want nil", err)
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
@@ -1 +1,3 @@
module github.com/magiconair/properties

go 1.12
29 changes: 17 additions & 12 deletions lex.go
Expand Up @@ -60,14 +60,15 @@ type stateFn func(*lexer) stateFn

// lexer holds the state of the scanner.
type lexer struct {
input string // the string being scanned
state stateFn // the next lexing function to enter
pos int // current position in the input
start int // start position of this item
width int // width of last rune read from input
lastPos int // position of most recent item returned by nextItem
runes []rune // scanned runes for this item
items chan item // channel of scanned items
input string // the string being scanned
state stateFn // the next lexing function to enter
pos int // current position in the input
start int // start position of this item
width int // width of last rune read from input
lastPos int // position of most recent item returned by nextItem
runes []rune // scanned runes for this item
items chan item // channel of scanned items
keepBackslash bool // true: doesn't drop the backslash \ that isn't precede any of " :=fnrt", default is false
}

// next returns the next rune in the input.
Expand Down Expand Up @@ -162,11 +163,12 @@ func (l *lexer) nextItem() item {
}

// lex creates a new scanner for the input string.
func lex(input string) *lexer {
func lex(input string, keepBackslash bool) *lexer {
l := &lexer{
input: input,
items: make(chan item),
runes: make([]rune, 0, 32),
input: input,
items: make(chan item),
runes: make([]rune, 0, 32),
keepBackslash: keepBackslash,
}
go l.run()
return l
Expand Down Expand Up @@ -321,6 +323,9 @@ func (l *lexer) scanEscapeSequence() error {

// silently drop the escape character and append the rune as is
default:
if l.keepBackslash {
l.appendRune('\\') // Assumes the escape character is put on purpose
}
l.appendRune(r)
return nil
}
Expand Down
6 changes: 5 additions & 1 deletion load.go
Expand Up @@ -45,6 +45,10 @@ type Loader struct {
// 404 are reported as errors. When set to true, missing files and 404
// status codes are not reported as errors.
IgnoreMissing bool

// KeepBackslash true: doesn't drop the backslash \ that isn't precede any of " :=fnrt", default is false
KeepBackslash bool

}

// Load reads a buffer into a Properties struct.
Expand Down Expand Up @@ -146,7 +150,7 @@ func (l *Loader) LoadURL(url string) (*Properties, error) {
}

func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) {
p, err := parse(convert(buf, enc))
p, err := parse(convert(buf, enc), l.KeepBackslash)
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions parser.go
Expand Up @@ -13,11 +13,12 @@ type parser struct {
lex *lexer
}

func parse(input string) (properties *Properties, err error) {
p := &parser{lex: lex(input)}
func parse(input string, keepBackslash bool) (properties *Properties, err error) {
p := &parser{lex: lex(input, keepBackslash)}
defer p.recover(&err)

properties = NewProperties()
properties.KeepBackslash = keepBackslash
key := ""
comments := []string{}

Expand Down
16 changes: 9 additions & 7 deletions properties.go
Expand Up @@ -54,7 +54,8 @@ type Properties struct {
// Pre-/Postfix for property expansion.
Prefix string
Postfix string

// KeepBackslash for lexer
KeepBackslash bool
// DisableExpansion controls the expansion of properties on Get()
// and the check for circular references on Set(). When set to
// true Properties behaves like a simple key/value store and does
Expand All @@ -75,17 +76,18 @@ type Properties struct {
// configuration for "${key}" expressions.
func NewProperties() *Properties {
return &Properties{
Prefix: "${",
Postfix: "}",
m: map[string]string{},
c: map[string][]string{},
k: []string{},
Prefix: "${",
Postfix: "}",
KeepBackslash: false,
m: map[string]string{},
c: map[string][]string{},
k: []string{},
}
}

// Load reads a buffer into the given Properties struct.
func (p *Properties) Load(buf []byte, enc Encoding) error {
l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion}
l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion, KeepBackslash: p.KeepBackslash}
newProperties, err := l.LoadBytes(buf)
if err != nil {
return err
Expand Down
37 changes: 27 additions & 10 deletions properties_test.go
Expand Up @@ -26,6 +26,15 @@ func init() {

// ----------------------------------------------------------------------------

// define test cases in the form of
// {"input", "key1", "value1", "key2", "value2", ...}
var preservedBackslashTests = [][]string{
// doesn't drop escape character
{"k\\zey = value", "k\\zey", "value"},
{"key = v\\zalue", "key", "v\\zalue"},
{"key1 = \\{name\\} \\\\{value}", "key1", "\\{name\\} \\\\{value}"},
}

// define test cases in the form of
// {"input", "key1", "value1", "key2", "value2", ...}
var complexTests = [][]string{
Expand Down Expand Up @@ -428,6 +437,12 @@ var setTests = []struct {

// ----------------------------------------------------------------------------

func TestPreservedBackslash(t *testing.T) {
for _, test := range preservedBackslashTests {
testKeyValue(t, true, test[0], test[1:]...)
}
}

// TestBasic tests basic single key/value combinations with all possible
// whitespace, delimiter and newline permutations.
func TestBasic(t *testing.T) {
Expand All @@ -438,7 +453,7 @@ func TestBasic(t *testing.T) {

func TestComplex(t *testing.T) {
for _, test := range complexTests {
testKeyValue(t, test[0], test[1:]...)
testKeyValue(t, false, test[0], test[1:]...)
}
}

Expand Down Expand Up @@ -787,7 +802,7 @@ func TestMustSet(t *testing.T) {

func TestWrite(t *testing.T) {
for _, test := range writeTests {
p, err := parse(test.input)
p, err := parse(test.input, false)

buf := new(bytes.Buffer)
var n int
Expand All @@ -806,7 +821,7 @@ func TestWrite(t *testing.T) {

func TestWriteComment(t *testing.T) {
for _, test := range writeCommentTests {
p, err := parse(test.input)
p, err := parse(test.input, false)

buf := new(bytes.Buffer)
var n int
Expand All @@ -824,7 +839,7 @@ func TestWriteComment(t *testing.T) {
}

func TestCustomExpansionExpression(t *testing.T) {
testKeyValuePrePostfix(t, "*[", "]*", "key=value\nkey2=*[key]*", "key", "value", "key2", "value")
testKeyValuePrePostfix(t, false, "*[", "]*", "key=value\nkey2=*[key]*", "key", "value", "key2", "value")
}

func TestPanicOn32BitIntOverflow(t *testing.T) {
Expand Down Expand Up @@ -928,7 +943,7 @@ func testWhitespaceAndDelimiterCombinations(t *testing.T, key, value string) {
}

input := fmt.Sprintf("%s%s%s%s%s%s", key, ws1, dl, ws2, value, nl)
testKeyValue(t, input, key, value)
testKeyValue(t, false, input, key, value)
}
}
}
Expand All @@ -937,14 +952,16 @@ func testWhitespaceAndDelimiterCombinations(t *testing.T, key, value string) {

// tests whether key/value pairs exist for a given input.
// keyvalues is expected to be an even number of strings of "key", "value", ...
func testKeyValue(t *testing.T, input string, keyvalues ...string) {
testKeyValuePrePostfix(t, "${", "}", input, keyvalues...)
func testKeyValue(t *testing.T, keepBackslash bool, input string, keyvalues ...string) {
testKeyValuePrePostfix(t, keepBackslash, "${", "}", input, keyvalues...)
}

// tests whether key/value pairs exist for a given input.
// keyvalues is expected to be an even number of strings of "key", "value", ...
func testKeyValuePrePostfix(t *testing.T, prefix, postfix, input string, keyvalues ...string) {
p, err := Load([]byte(input), ISO_8859_1)
func testKeyValuePrePostfix(t *testing.T, keepBackslash bool, prefix, postfix, input string, keyvalues ...string) {
p := NewProperties()
p.KeepBackslash = keepBackslash
err := p.Load([]byte(input), ISO_8859_1)
assert.Equal(t, err, nil)
p.Prefix = prefix
p.Postfix = postfix
Expand All @@ -970,7 +987,7 @@ func assertKeyValues(t *testing.T, input string, p *Properties, keyvalues ...str
}

func mustParse(t *testing.T, s string) *Properties {
p, err := parse(s)
p, err := parse(s, false)
if err != nil {
t.Fatalf("parse failed with %s", err)
}
Expand Down