-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
191 lines (159 loc) · 5.49 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package main
import (
"flag" // Needed for CLI parsing
"fmt" // Needed for printing
"net/http" // Needed for http call
"os" // Needed to do len(os.Args)
"regexp" // Needed for space trimming
"strconv" // Needed to convert string to float
"strings" // Needed for space trimming
"github.com/PuerkitoBio/goquery" // Needed for HTML table parsing
)
const MAINE_SUPPLIER_URL = "https://www.maine.gov/meopa/electricity/electricity-supply"
// Offer from the electric supplier
type ElectricOffer struct {
CMPRate float64
EMERARate float64
FixedTerm string
}
// Electric supplier
type ElectricSupplier struct {
Company string
Offers []ElectricOffer
EarlyTermFee string
ContactNumber string
}
func main() {
fmt.Println("Maine electricity cost comparison tool.")
// Vars
var currentCost float64
var electricProvider string
var lowestProvider string
// Check to see if the user supplied args, or wants to take input.
if len(os.Args) > 1 {
flag.Float64Var(¤tCost, "current", 10.0, "Current cost of your electricity in ¢/kWh")
flag.StringVar(&electricProvider, "provider", "CMP", "Current electric provider, defaults to CMP")
flag.Parse()
} else {
fmt.Printf("Enter your current price for electricity (¢/kWh): ")
_, err := fmt.Scanf("%f\n", ¤tCost)
handleErr(err)
fmt.Printf("Enter your current provider. (CMP or EMERA): ")
_, err = fmt.Scanf("%s\n", &electricProvider)
electricProvider = strings.ToUpper(electricProvider)
handleErr(err)
}
// More vars
electricProvider = strings.ToUpper(electricProvider)
lowestCost := currentCost
// Grab all the rows and build the list
rows := getHeadingRows(MAINE_SUPPLIER_URL)
supplierList := buildSupplierList(rows)
// Loop for every supplier and print all their offerings
for _, supplier := range supplierList {
fmt.Println("Supplier:", supplier.Company)
fmt.Println("Offerings:")
fmt.Println("\tCMP Rate\tEmera Rate\tTerm")
for _, offer := range supplier.Offers {
// This should be revisited at some point to make sure it doesn't break with weird values.
fmt.Printf("\t¢%.02f\t\t¢%.02f\t\t%s\r\n", offer.CMPRate, offer.EMERARate, offer.FixedTerm)
switch electricProvider {
case "CMP":
if lowestCost > offer.CMPRate {
lowestCost = offer.CMPRate
lowestProvider = supplier.Company
}
case "EMERA":
if lowestCost > offer.EMERARate {
lowestCost = offer.EMERARate
lowestProvider = supplier.Company
}
}
}
fmt.Println("Early Termination Fee:", supplier.EarlyTermFee)
fmt.Println("Contact Number:", supplier.ContactNumber)
fmt.Println("-----")
}
fmt.Println("Current Cost:", currentCost)
fmt.Printf("Lowest Cost Found: %.02f from %s\r\n", lowestCost, lowestProvider)
fmt.Println("If there's a provider offering cheaper electricity you can switch and save money.")
fmt.Println("There's no change in equipment, lines, or anything. Just a billing change with your provider.")
fmt.Println("For more details please visit: https://www.maine.gov/meopa/electricity/electricity-supply#CEPrates")
if len(os.Args) < 2 {
fmt.Println("Press enter to exit.")
fmt.Scanf("%v\n", &electricProvider)
}
}
// Build out a slice of electric suppliers from the HTML table
func buildSupplierList(rows [][]string) []ElectricSupplier {
var supplierList []ElectricSupplier
var supplier ElectricSupplier
var offering ElectricOffer
var lastSupplier = supplier
// Loop for all the rows and hope they don't change the format often.
for _, row := range rows {
if len(row) == 6 {
if lastSupplier.Company != "" {
supplierList = append(supplierList, lastSupplier)
}
var supplier ElectricSupplier
supplier.Company = row[0]
supplier.EarlyTermFee = row[4]
supplier.ContactNumber = row[5]
for x := 1; x <= 2; x++ {
if strings.IndexAny(row[1], " (%)") > -1 {
rowSplit := strings.Split(row[1], " ")
row[1] = rowSplit[0]
}
}
offering.CMPRate, _ = strconv.ParseFloat(row[1], 64)
offering.EMERARate, _ = strconv.ParseFloat(row[2], 64)
offering.FixedTerm = row[3]
supplier.Offers = append(supplier.Offers, offering)
lastSupplier = supplier
} else if len(row) == 3 {
var offering ElectricOffer
for x := 0; x <= 1; x++ {
if strings.IndexAny(row[x], " (%)") > -1 {
rowSplit := strings.Split(row[x], " ")
row[x] = rowSplit[0]
}
}
offering.CMPRate, _ = strconv.ParseFloat(row[0], 64)
offering.EMERARate, _ = strconv.ParseFloat(row[1], 64)
offering.FixedTerm = row[2]
lastSupplier.Offers = append(lastSupplier.Offers, offering)
}
}
return supplierList
}
// Get the headings and rows from the website
// Thanks to Salmoni for some of this logic.
// https://gist.github.com/salmoni/27aee5bb0d26536391aabe7f13a72494
func getHeadingRows(supplierURL string) [][]string {
var row []string
var rows [][]string
spaceRemoval := regexp.MustCompile(`(\s\s)`)
resp, err := http.Get(supplierURL)
handleErr(err)
defer resp.Body.Close()
doc, err := goquery.NewDocumentFromReader(resp.Body)
handleErr(err)
doc.Find("table").Each(func(index int, table *goquery.Selection) {
table.Find("tr").Each(func(indextr int, tablerow *goquery.Selection) {
tablerow.Find("td").Each(func(indexth int, cell *goquery.Selection) {
row = append(row, spaceRemoval.ReplaceAllString(strings.TrimSpace(cell.Text()), " "))
})
rows = append(rows, row)
row = nil
})
})
return rows
}
// TODO: Actually implement error handling.
// For now, just panic and print the error.
func handleErr(err error) {
if err != nil {
panic(err)
}
}