Skip to content

Commit

Permalink
Merge pull request #19 from mkobetic/tag-register-tweaks
Browse files Browse the repository at this point in the history
tag/register tweaks
  • Loading branch information
mkobetic authored Feb 13, 2024
2 parents a8b367e + 5e5120b commit 8e050be
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 23 deletions.
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

#### ofx2coin

* allow tagging imported transactions
* duplicate elimination too aggressive with identical transactions (e.g. 2x ROGERS top up for cell phones)
? duplicate transactions from the same source/file should be kept?
* sanitize sensitive information, account/cc numbers
Expand Down
11 changes: 8 additions & 3 deletions cmd/coin/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ Lists or aggregate postings from the specified account.`)
// filtering options
cmd.Var(&cmd.begin, "b", "begin register from this date")
cmd.Var(&cmd.end, "e", "end register on this date")
cmd.StringVar(&cmd.payee, "p", "", "use only postings matching the payee (regex)")
cmd.StringVar(&cmd.tag, "t", "", "use only postings matching the tag[:value] (regex)")
cmd.StringVar(&cmd.payee, "p", "", "use only postings matching the payee ([!]regex)")
cmd.StringVar(&cmd.tag, "t", "", "use only postings matching the tag[:value] ([!]regex)")
// aggregation options
cmd.BoolVar(&cmd.weekly, "w", false, "aggregate postings by week")
cmd.BoolVar(&cmd.monthly, "m", false, "aggregate postings by month")
Expand Down Expand Up @@ -193,10 +193,15 @@ func (cmd *cmdRegister) period() *reducer {
func (cmd *cmdRegister) trim(ps []*coin.Posting) postings {
ps = trim(ps, cmd.begin, cmd.end)
if len(cmd.payee) > 0 {
inverted := false
if cmd.payee[0] == '!' {
inverted = true
cmd.payee = cmd.payee[1:]
}
var pps []*coin.Posting
r := regexp.MustCompile("(?i)" + cmd.payee)
for _, p := range ps {
if r.MatchString(p.Transaction.Description) {
if match := r.MatchString(p.Transaction.Description); match && !inverted || !match && inverted {
pps = append(pps, p)
}
}
Expand Down
54 changes: 38 additions & 16 deletions cmd/coin/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ func init() {

type cmdTags struct {
flagsWithUsage
values bool
fValues bool
fAccounts bool

results map[string][]string
accounts map[string][]string
}

func (*cmdTags) newCommand(names ...string) command {
Expand All @@ -25,7 +29,8 @@ func (*cmdTags) newCommand(names ...string) command {
setUsage(cmd.FlagSet, `tags [flags] [NAMEREX]
List tags matching the NAMEREX.`)
cmd.BoolVar(&cmd.values, "v", false, "print tag values if applicable")
cmd.BoolVar(&cmd.fValues, "v", false, "print tag values if applicable")
cmd.BoolVar(&cmd.fAccounts, "a", false, "print account names where tag is used")
return &cmd
}

Expand All @@ -38,24 +43,49 @@ func (cmd *cmdTags) execute(f io.Writer) {
if cmd.NArg() > 0 {
nrex = regexp.MustCompile("(?i)" + cmd.Arg(0))
}
results := make(map[string][]string)
cmd.results = make(map[string][]string)
cmd.accounts = make(map[string][]string)
for _, t := range coin.Transactions {
collectKeys(nrex, t.Tags, results)
accounts := [](*coin.Account){}
for _, p := range t.Postings {
collectKeys(nrex, p.Tags, results)
cmd.collectKeys(nrex, p.Tags, p.Account)
if cmd.fAccounts {
accounts = append(accounts, p.Account)
}
}
cmd.collectKeys(nrex, t.Tags, accounts...)
}
for _, k := range sortAndClean(results) {
vs := strings.Join(results[k], `", "`)
for _, k := range sortAndClean(cmd.results) {
vs := strings.Join(cmd.results[k], `", "`)
colon := ""
if len(vs) > 0 {
colon = ":"
}
if cmd.values && len(vs) > 0 {
if cmd.fValues && len(vs) > 0 {
fmt.Fprintf(f, `%s%s "%s"`+"\n", k, colon, vs)
} else {
fmt.Fprintf(f, "%s%s\n", k, colon)
}
if cmd.fAccounts {
for _, a := range cmd.accounts[k] {
fmt.Fprintf(f, "\t%s\n", a)
}
}
}
}

func (cmd *cmdTags) collectKeys(nrex *regexp.Regexp, tags coin.Tags, accounts ...*coin.Account) {
for k, v := range tags {
if nrex == nil || nrex.MatchString(k) {
cmd.results[k] = add(cmd.results[k], v)
if cmd.fAccounts {
list := cmd.accounts[k]
for _, a := range accounts {
list = add(list, a.FullName)
}
cmd.accounts[k] = list
}
}
}
}

Expand All @@ -76,14 +106,6 @@ func clean(list []string) []string {
return list
}

func collectKeys(nrex *regexp.Regexp, tags coin.Tags, results map[string][]string) {
for k, v := range tags {
if nrex == nil || nrex.MatchString(k) {
results[k] = add(results[k], v)
}
}
}

func add(list []string, key string) []string {
for _, v := range list {
if v == key {
Expand Down
12 changes: 9 additions & 3 deletions tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strings"
)

var tagREX = regexp.MustCompile(`#(?P<key>\w+)(:\s*(?P<value>[^,]+\S)\s*(,|$))?`)
var tagREX = regexp.MustCompile(`#(?P<key>[\w-/]+)(:\s*(?P<value>[^,]+\S)\s*(,|$))?`)
var tagREXKey = tagREX.SubexpIndex("key")
var tagREXValue = tagREX.SubexpIndex("value")

Expand Down Expand Up @@ -56,27 +56,33 @@ func (t Tags) Keys() (keys []string) {
// matched against a tag key and optionally a tag value.
type TagMatcher struct {
Key, Value *regexp.Regexp
inverted bool // match if key/value not matching
}

func NewTagMatcher(exp string) *TagMatcher {
if len(exp) == 0 {
return nil
}
inverted := false
if exp[0] == '!' {
inverted = true
exp = exp[1:]
}
parts := strings.SplitN(exp, ":", 2)
var key, value *regexp.Regexp
key = regexp.MustCompile("(?i)" + parts[0])
if len(parts) > 1 {
value = regexp.MustCompile("(?i)" + parts[1])
}
return &TagMatcher{Key: key, Value: value}
return &TagMatcher{Key: key, Value: value, inverted: inverted}
}

func (m *TagMatcher) Match(tags Tags) bool {
if tags == nil {
return false
}
for k, v := range tags {
if m.Key.MatchString(k) && (m.Value == nil || m.Value.MatchString(v)) {
if match := m.Key.MatchString(k) && (m.Value == nil || m.Value.MatchString(v)); match && !m.inverted || !match && m.inverted {
return true
}
}
Expand Down

0 comments on commit 8e050be

Please sign in to comment.