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

add configparser options #17

Merged
merged 33 commits into from
Apr 27, 2023
Merged

add configparser options #17

merged 33 commits into from
Apr 27, 2023

Conversation

emar-kar
Copy link

@emar-kar emar-kar commented Jan 28, 2023

This PR adds options which are available in python ConfigParser constructor.

List of added options:

  • Delimiters - allows to set custom key-value pair delimiters.
  • CommentPrefixes - allows to set custom comment line prefix. If line starts with one of the given Prefixes it will be passed during parsing.
  • InlineCommentPrefixes - allows to set custom inline comment delimiter. This option checks if the line contains any of the given Prefixes and if so, splits the string by the prefix and returns the 0 index of the slice.
  • MultilinePrefixes - allows to set custom multiline values prefixes. This option checks if the line starts with one of the given Prefixes and if so, counts it as a part of the current value.
  • Strict - if set to true, parser will return error for duplicates of sections or options in one source.
  • AllowEmptyLines - if set to true allows multiline values to include empty lines as their part. Otherwise the value will be parsed until an empty line or the line which does not start with one of the allowed multiline prefixes.
  • Interpolation - allows to set custom behaviour for values interpolation. Interface was added, which defaults to chainmap.ChainMap instance.
type Interpolator interface {
	Add(...chainmap.Dict)
	Len() int
	Get(string) string
}
  • Converters - allows to set custom values parsers.
type ConvertFunc func(string) (any, error)

ConvertFunc can modify requested value if needed e.g.,

package main

import (
	"fmt"
	"strings"

	"github.com/bigkevmcd/go-configparser"
)

func main() {
	stringConv := func(s string) (any, error) {
		return s + "_updated", nil
	}

	conv := configparser.Converter{
		configparser.String: stringConv,
	}

	p, err := configparser.ParseReaderWithOptions(
		strings.NewReader("[section]\noption=value\n\n"),
		configparser.Converters(conv),
	)
	// handle err

	v, err := p.Get("section", "option")
	// handle err

	fmt.Println(v == "value_updated") // true
}

Those functions triggered inside ConfigParser.Get* methods if presented and wraps the return value.

NOTE: Since ConvertFunc returns any, the caller should guarantee type assertion to the requested type after custom processing!

type Converter map[string]ConvertFunc

Converter is a map type, which supports int (for int64), string, bool, float (for float64) keys.


Default options, which are always preset:

func defaultOptions() *options {
	return &options{
		interpolation:     chainmap.New(),
		defaultSection:    defaultSectionName,
		delimiters:        ":=",
		commentPrefixes:   Prefixes{"#", ";"},
		multilinePrefixes: Prefixes{"\t", " "},
		converters: Converter{
			StringConv: defaultGet,
			IntConv:    defaultGetInt64,
			FloatConv:  defaultGetFloat64,
			BoolConv:   defaultGetBool,
		},
	}
}

* update keyValue regexp
* add check if section was found before parsing keyValue
* update value slice index
* add tests
* add Options to control Parser behavior
* add NewWithOptions
* add ParseReaderWithOptions
* add ParseWithOptions

moved main parser logic into  configparser.ParseReader
* interpolation
* commentPrefixes
* inlineCommentPrefixes
* defaultSection
* delimeters
* converters
* strict

TODO: investigate `emptyLines` realisation
@emar-kar emar-kar marked this pull request as draft January 28, 2023 04:09
@bigkevmcd
Copy link
Owner

I do want to land these changes, I just need to find some time to read over them.

@emar-kar
Copy link
Author

emar-kar commented Jan 29, 2023

I do want to land these changes, I just need to find some time to read over them.

I still do some things here and there

options.go Outdated
strict bool

// TODO: under investigation, have no effect now.
emptyLines bool
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bigkevmcd I was thinking recently about this option, and actually I think it can be implemented, but in this case we will need to change the basics of the file parser. Since it's related to #14 issue, the process of parsing the multiline values should be added first. In this case, I would like to leave it as it is right now and come back to it after those changes will be reviewed

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will make time over the weekend to review this and move it forward

Copy link
Author

@emar-kar emar-kar Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added multiline value support and empty lines flag

@emar-kar emar-kar marked this pull request as ready for review February 2, 2023 19:39
@emar-kar emar-kar mentioned this pull request Feb 2, 2023
@emar-kar emar-kar changed the title add new options add configparser options Feb 2, 2023
Copy link
Owner

@bigkevmcd bigkevmcd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, some small changes, but otherwise, I'm happy enough with this.

configparser.go Outdated
curSect = newSection(section)
p.config[section] = curSect
} else if p.opt.strict {
return fmt.Errorf("section %q error: %w", section, ErrAlreadyExist)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure wrapping a sentinel error is the right way to do this.

I'd be inclined to define an AlreadyExistsError type, which can contain the section with .Error() string which would return this message.

Take a look at the stdlib PathError for an example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I also use it here to report duplicate option. As I see PathError still have error field and Unwrap method and Op. Is it worth it to make the whole new structure for the error which actually can be triggered twice? Imho wrapped error is more informative, but at the same time can be easily unwrapped in the user code to assert it with errors.Is

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed wrapped error and let it be just text description, without any specific type

options.go Outdated
type Prefixes []string

// AtFirst checks if str starts with any of the prefixes.
func (pr Prefixes) AtFirst(str string) bool {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just MatchesPrefix for this? or even HasPrefix ?

AtFirst doesn't really say what this does?

func (pr Prefixes) Split(str string) string {
for _, p := range pr {
if strings.Contains(str, p) {
return strings.Split(str, p)[0]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that these are prefixes, should this use HasPrefix ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is used for inline comments e.g. option = value # this is a comment. Thus, here it checks if given string contains any of the Prefixes

@@ -15,6 +15,10 @@ func New(dicts ...Dict) *ChainMap {
return chainMap
}

func (c *ChainMap) Add(dicts ...Dict) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exported and should have a doc comment?

@emar-kar
Copy link
Author

@bigkevmcd should we add some info on the options usage in readme file? Like the same text in the MR description?

@emar-kar
Copy link
Author

@bigkevmcd any updates on this one?

@bigkevmcd
Copy link
Owner

@emar-kar Apologies, I'll get to it this week.

@emar-kar
Copy link
Author

Recent updates will close both #14 and #18 issues

Copy link
Owner

@bigkevmcd bigkevmcd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emar-kar Thanks for this, looks good.

@bigkevmcd bigkevmcd merged commit 380708e into bigkevmcd:main Apr 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants