Browse Source

created new types and some refactoring

bmallred 10 years ago
parent
commit
20b2ccd66d
7 changed files with 163 additions and 118 deletions
  1. 1 0
      .gitignore
  2. 45 0
      account.go
  3. 8 4
      cash.go
  4. 21 93
      commit.go
  5. 0 7
      example.ledger
  6. 0 14
      general.ledger
  7. 88 0
      transaction.go

+ 1 - 0
.gitignore

21
21
22
*.exe
22
*.exe
23
cash
23
cash
24
*.ledger

+ 45 - 0
account.go

1
package main
2
3
import (
4
	"errors"
5
	"fmt"
6
	"math/big"
7
	"strings"
8
)
9
10
type Account struct {
11
	Name   string
12
	Debit  bool
13
	Amount *big.Rat
14
}
15
16
// Convert from a string to an account
17
func (a *Account) FromString(text string) error {
18
	fields := strings.Split(text, "\t")
19
20
	if len(fields) != 3 {
21
		return errors.New("Invalid account format")
22
	}
23
24
	debit := true
25
	if strings.HasPrefix(fields[2], "-") {
26
		debit = false
27
	}
28
29
	a.Debit = debit
30
	a.Name = fields[1]
31
	a.Amount = new(big.Rat)
32
	a.Amount.SetString(fields[2][1:])
33
34
	return nil
35
}
36
37
// Convert the account to string format
38
func (a *Account) ToString() string {
39
	symbol := "-"
40
	if a.Debit {
41
		symbol = "+"
42
	}
43
44
	return fmt.Sprintf("\t%s\t%s%s\n", a.Name, symbol, a.Amount.FloatString(2))
45
}

+ 8 - 4
cash.go

15
)
15
)
16
16
17
var (
17
var (
18
	Ledger            = "general.ledger"
19
	TransactionFormat = "%s\t%s\t%s"
20
	AccountFormat     = "\t%s\t%s"
21
	PendingFile       = filepath.Join(os.TempDir(), "pending.ledger")
18
	LedgerFile  = "general.ledger"
19
	PendingFile = filepath.Join(os.TempDir(), "pending.ledger")
22
)
20
)
23
21
24
// Initialize the application
22
// Initialize the application
25
func init() {
23
func init() {
24
	if _, err := os.Stat(LedgerFile); os.IsNotExist(err) {
25
		_, err = os.Create(LedgerFile)
26
		check(err)
27
		log.Println("Created ledger file at", LedgerFile)
28
	}
29
26
	if _, err := os.Stat(PendingFile); os.IsNotExist(err) {
30
	if _, err := os.Stat(PendingFile); os.IsNotExist(err) {
27
		_, err = os.Create(PendingFile)
31
		_, err = os.Create(PendingFile)
28
		check(err)
32
		check(err)

+ 21 - 93
commit.go

2
2
3
import (
3
import (
4
	"errors"
4
	"errors"
5
	"fmt"
6
	"io/ioutil"
5
	"io/ioutil"
7
	"math/big"
8
	"os"
6
	"os"
9
	"strings"
7
	"strings"
10
	"time"
8
	"time"
35
	project := parseProject(args)
33
	project := parseProject(args)
36
	description := parseDescription(args, project)
34
	description := parseDescription(args, project)
37
35
38
	writeTransaction(date.Format("2006-01-02"), project, description)
36
	writeTransaction(date, project, description)
39
}
37
}
40
38
41
// Parse the given string to extract a proper date
39
// Parse the given string to extract a proper date
91
	return strings.Replace(strings.Join(fields, " "), "  ", " ", -1)
89
	return strings.Replace(strings.Join(fields, " "), "  ", " ", -1)
92
}
90
}
93
91
94
type Account struct {
95
	Name   string
96
	Debit  bool
97
	Amount *big.Rat
98
}
99
100
type Transaction struct {
101
	Date        time.Time
102
	Project     string
103
	Description string
104
	Accounts    []Account
105
}
106
107
func (t *Transaction) FromString(text string) error {
108
	// Parse the lines of text
109
	lines := strings.Split(text, "\n")
110
	for i, line := range lines {
111
		fields := strings.Split(line, "\t")
112
113
		switch i {
114
		case 0:
115
			date, err := parseDate(fields[0])
116
			check(err)
117
			project := fields[1]
118
			description := ""
119
			if len(fields) > 2 {
120
				description = strings.Join(fields[2:], " ")
121
			}
122
123
			t = &Transaction{
124
				Date:        date,
125
				Project:     project,
126
				Description: description,
127
				Accounts:    []Account{},
128
			}
129
			break
130
131
		default:
132
			if len(fields) != 3 {
133
				break
134
			}
135
136
			account := fields[1]
137
			debit := true
138
139
			if strings.HasPrefix(fields[2], "-") {
140
				debit = false
141
			}
142
			value := new(big.Rat)
143
			value.SetString(fields[2][1:])
144
145
			t.Accounts = append(
146
				t.Accounts,
147
				Account{
148
					Name:   account,
149
					Debit:  debit,
150
					Amount: value,
151
				})
152
153
			break
154
		}
155
	}
156
157
	if len(t.Accounts) == 0 {
158
		return errors.New("Transaction does not have any accounts")
159
	}
160
161
	// Check that they balance
162
	balance := new(big.Rat)
163
	for _, a := range t.Accounts {
164
		if a.Debit {
165
			balance.Add(balance, a.Amount)
166
		} else {
167
			balance.Sub(balance, a.Amount)
168
		}
169
	}
170
	if balance.FloatString(2) != "0.00" {
171
		return errors.New("Transaction does not balance")
172
	}
173
174
	return nil
175
}
176
177
// Write a transaction line where there is a pending transaction
92
// Write a transaction line where there is a pending transaction
178
func writeTransaction(date, project, description string) {
93
func writeTransaction(date time.Time, project, description string) {
179
	if !hasPendingTransaction() {
94
	if !hasPendingTransaction() {
180
		check(errors.New("No pending transaction to write"))
95
		check(errors.New("No pending transaction to write"))
181
	}
96
	}
183
	pending, err := ioutil.ReadFile(PendingFile)
98
	pending, err := ioutil.ReadFile(PendingFile)
184
	check(err)
99
	check(err)
185
100
186
	// Find the line containing @pending and replace it with our transaction
187
	s := fmt.Sprintf("%s\t%s\t%s\n%s\n", date, project, description, string(pending))
188
	var t Transaction
189
	err = t.FromString(s)
101
	t := Transaction{
102
		Date:        date,
103
		Project:     project,
104
		Description: description,
105
		Accounts:    []Account{},
106
	}
107
108
	lines := strings.Split(strings.TrimRight(string(pending), "\n"), "\n")
109
	for _, line := range lines {
110
		var a Account
111
		err = a.FromString(line)
112
		check(err)
113
114
		t.Accounts = append(t.Accounts, a)
115
	}
116
117
	err = t.CheckBalance()
190
	check(err)
118
	check(err)
191
119
192
	file, err := os.OpenFile(Ledger, os.O_APPEND|os.O_WRONLY, 0666)
120
	file, err := os.OpenFile(LedgerFile, os.O_APPEND|os.O_WRONLY, 0666)
193
	check(err)
121
	check(err)
194
	defer file.Close()
122
	defer file.Close()
195
	_, err = file.WriteString(s)
123
	_, err = file.WriteString(t.ToString())
196
	check(err)
124
	check(err)
197
}
125
}

+ 0 - 7
example.ledger

1
2015-01-03  @general    Some other stuff
2
    #cash       -10.00
3
    #services   +10.00
4
2015-01-03  @general    Multiple accounts example
5
    #cash       +10.00
6
    #liability  -5.00
7
    #a          -5.00

+ 0 - 14
general.ledger

1
2015-01-03  @general    Some other stuff
2
    cash       -10.00
3
    services   +10.00
4
5
2015-01-03  @general    Multiple accounts example
6
    cash       +10.00
7
    liability  -5.00
8
    a          -5.00
9
10
2015-01-05	@general	blah
11
	savings	-10.00
12
	cash	+5.00
13
	sheep	+5.00
14

+ 88 - 0
transaction.go

1
package main
2
3
import (
4
	"errors"
5
	"fmt"
6
	"math/big"
7
	"strings"
8
	"time"
9
)
10
11
type Transaction struct {
12
	Date        time.Time
13
	Project     string
14
	Description string
15
	Accounts    []Account
16
}
17
18
// Create a transaction from a string
19
func (t *Transaction) FromString(text string) {
20
	// Parse the lines of text
21
	lines := strings.Split(text, "\n")
22
	for i, line := range lines {
23
		switch i {
24
		case 0:
25
			fields := strings.Split(line, "\t")
26
27
			date, err := parseDate(fields[0])
28
			check(err)
29
30
			project := fields[1]
31
32
			description := ""
33
			if len(fields) > 2 {
34
				description = strings.Join(fields[2:], " ")
35
			}
36
37
			t = &Transaction{
38
				Date:        date,
39
				Project:     project,
40
				Description: description,
41
				Accounts:    []Account{},
42
			}
43
			break
44
45
		default:
46
			var account Account
47
			err := account.FromString(line)
48
			check(err)
49
50
			t.Accounts = append(t.Accounts, account)
51
			break
52
		}
53
	}
54
}
55
56
// Check the transaction to ensure it is balanced
57
func (t *Transaction) CheckBalance() error {
58
	if len(t.Accounts) == 0 {
59
		return errors.New("Transaction does not have any accounts")
60
	}
61
62
	// Check that they balance
63
	balance := new(big.Rat)
64
	for _, a := range t.Accounts {
65
		if a.Debit {
66
			balance.Add(balance, a.Amount)
67
		} else {
68
			balance.Sub(balance, a.Amount)
69
		}
70
	}
71
72
	if balance.FloatString(2) != "0.00" {
73
		return errors.New("Transaction does not balance")
74
	}
75
76
	return nil
77
}
78
79
// Convert a transaction to string format
80
func (t *Transaction) ToString() string {
81
	accounts := ""
82
83
	for _, account := range t.Accounts {
84
		accounts += account.ToString()
85
	}
86
87
	return fmt.Sprintf("%s\t%s\t%s\n%s\n", t.Date.Format("2006-01-02"), t.Project, t.Description, string(accounts))
88
}