瀏覽代碼

add pending transaction and add transaction if pending available

bmallred 10 年之前
父節點
當前提交
9e0e8c1b0e
共有 4 個文件被更改,包括 278 次插入1 次删除
  1. 2 1
      .gitignore
  2. 257 0
      cash.go
  3. 9 0
      doc.go
  4. 10 0
      example.ledger

+ 2 - 1
.gitignore

@ -19,4 +19,5 @@ _cgo_export.*
19 19
20 20
_testmain.go
21 21
22
*.exe
22
*.exe
23
cash

+ 257 - 0
cash.go

@ -0,0 +1,257 @@
1
package main
2
3
import (
4
	"bytes"
5
	"errors"
6
	"fmt"
7
	"log"
8
	"os"
9
	"strings"
10
	"time"
11
12
	"github.com/codegangsta/cli"
13
)
14
15
const (
16
	// Application name
17
	APP_NAME = "cash"
18
19
	// Application usage description
20
	APP_USAGE = "counting coins"
21
22
	// Application version
23
	APP_VER = "0.0.0"
24
)
25
26
var (
27
	// Ledger file name
28
	Ledger            = "example.ledger"
29
	TransactionFormat = "%s\t%s\t%s"
30
	AccountFormat     = "\t%s\t%s"
31
)
32
33
// Application entry point
34
func main() {
35
	// Flags pertaining to a transaction action
36
	transactionFlags := []cli.Flag{
37
		cli.StringFlag{
38
			Name:  "date",
39
			Value: time.Now().UTC().Format("2006-01-02"),
40
			Usage: "",
41
		},
42
	}
43
44
	app := cli.NewApp()
45
	app.Name = APP_NAME
46
	app.Usage = APP_USAGE
47
	app.Version = APP_VER
48
	app.Commands = []cli.Command{
49
		{
50
			Name:      "credit",
51
			ShortName: "cr",
52
			Usage:     "",
53
			Action:    actionCredit,
54
		},
55
		{
56
			Name:      "debit",
57
			ShortName: "dr",
58
			Usage:     "",
59
			Action:    actionDebit,
60
		},
61
		{
62
			Name:      "status",
63
			ShortName: "stat",
64
			Usage:     "",
65
			Action:    actionStatus,
66
		},
67
		{
68
			Name:      "commit",
69
			ShortName: "c",
70
			Usage:     "",
71
			Action:    actionCommit,
72
			Flags:     transactionFlags,
73
		},
74
		{
75
			Name:      "list",
76
			ShortName: "ls",
77
			Usage:     "",
78
			Action:    actionList,
79
		},
80
	}
81
82
	app.Run(os.Args)
83
}
84
85
// Helper function to check for fatal errors
86
func check(e error) {
87
	if e != nil {
88
		log.Fatal(fmt.Sprintf("Error: %s", e))
89
	}
90
}
91
92
// Add a credit to the pending transaction
93
func actionCredit(c *cli.Context) {
94
	addPendingTransaction()
95
96
	// Format: /t{account}/t-{value}
97
}
98
99
// Add a debit to the pending transaction
100
func actionDebit(c *cli.Context) {
101
	addPendingTransaction()
102
103
	// Format: /t{account}/t+{value}
104
}
105
106
// Display the current status of the ledger
107
func actionStatus(c *cli.Context) {
108
	log.Println(hasPendingTransaction())
109
}
110
111
// Commit the pending transaction
112
func actionCommit(c *cli.Context) {
113
	date := parseDate(c.String("date"))
114
	if date == "" {
115
		log.Fatal("Invalid transaction date")
116
	}
117
118
	args := c.Args()
119
	project := parseProject(args)
120
	description := parseDescription(args, project)
121
122
	err := writeTransaction(date, project, description)
123
	if err != nil {
124
		log.Fatal(err)
125
	}
126
}
127
128
// List the ledger contents
129
func actionList(c *cli.Context) {
130
}
131
132
// Format the ledger so it is human readable
133
func formatLedger() {
134
}
135
136
// Determines if there is currently a pending transaction in the ledger
137
func hasPendingTransaction() bool {
138
	file, err := os.Open(Ledger)
139
	check(err)
140
	defer file.Close()
141
142
	info, err := file.Stat()
143
	check(err)
144
	size := info.Size()
145
	if size > 1024 {
146
		size = 1024
147
	}
148
149
	_, err = file.Seek(size*-1, 2)
150
	check(err)
151
152
	buffer := make([]byte, size)
153
	_, err = file.Read(buffer)
154
	check(err)
155
156
	return strings.Contains(string(buffer), "@pending")
157
}
158
159
// Adds a pending transaction if one is not already present
160
func addPendingTransaction() {
161
	if !hasPendingTransaction() {
162
		file, err := os.OpenFile(Ledger, os.O_APPEND|os.O_WRONLY, 0666)
163
		check(err)
164
		defer file.Close()
165
166
		_, err = file.WriteString("@pending")
167
		check(err)
168
	}
169
}
170
171
// Parse the given string to extract a proper date
172
func parseDate(in string) string {
173
	formats := []string{
174
		"2006-01-02",
175
		"2006/01/02",
176
		"2006-1-2",
177
		"2006/1/2",
178
		"01-02-2006",
179
		"01/02/2006",
180
		"1-2-2006",
181
		"1/2/2006",
182
		"Jan 2, 2006",
183
		"Jan 02, 2006",
184
		"2 Jan 2006",
185
		"02 Jan 2006",
186
	}
187
188
	for _, f := range formats {
189
		d, err := time.Parse(f, in)
190
		if err == nil {
191
			return d.Format(formats[0])
192
		}
193
	}
194
195
	return ""
196
}
197
198
// Parse a given string to extract a project name
199
func parseProject(fields []string) string {
200
	project := "@general"
201
202
	for i := 0; i < len(fields); i++ {
203
		if strings.HasPrefix(fields[i], "@") {
204
			project = fields[i]
205
			break
206
		}
207
	}
208
209
	return project
210
}
211
212
// Parse the description from the arguments
213
func parseDescription(fields []string, project string) string {
214
	for i := 0; i < len(fields); i++ {
215
		if fields[i] == project {
216
			fields[i] = ""
217
			break
218
		}
219
	}
220
221
	return strings.Replace(strings.Join(fields, " "), "  ", " ", -1)
222
}
223
224
// Write a transaction line where there is a pending transaction
225
func writeTransaction(date, project, description string) error {
226
	if !hasPendingTransaction() {
227
		return errors.New("No pending transaction to write")
228
	}
229
230
	file, err := os.OpenFile(Ledger, os.O_RDWR, 0666)
231
	check(err)
232
	defer file.Close()
233
234
	info, err := file.Stat()
235
	check(err)
236
	size := info.Size()
237
	if size > 1024 {
238
		size = 1024
239
	}
240
241
	_, err = file.Seek(size*-1, 2)
242
	check(err)
243
244
	buffer := make([]byte, size)
245
	_, err = file.Read(buffer)
246
	check(err)
247
248
	// Find the line containing @pending and replace it with our transaction
249
	line := fmt.Sprintf("%s\t%s\t%s", date, project, description)
250
	buffer = bytes.Replace(buffer, []byte("@pending"), []byte(line), -1)
251
252
	offset := info.Size() - size
253
	_, err = file.WriteAt(buffer, offset)
254
	check(err)
255
256
	return nil
257
}

+ 9 - 0
doc.go

@ -0,0 +1,9 @@
1
package main
2
3
/*
4
Subcommands
5
	ledger
6
	period <start date> <stop date>
7
	add
8
	delete <transaction>
9
*/

+ 10 - 0
example.ledger

@ -0,0 +1,10 @@
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
8
2015-01-03  @something
9
    #cash       +2.00
10
@pending