Commit b80ee4b2bc37bdc1ab97b7a2eb302badaa906cd2
1 parent
832243a5df
Exists in
master
new stuff
Showing
9 changed files
with
276 additions
and
140 deletions
Show diff stats
date_util.go
File was created | 1 | package webutility | |
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "time" | ||
6 | ) | ||
7 | |||
8 | const ( | ||
9 | YYYYMMDD_sl = "2006/01/02" | ||
10 | YYYYMMDD_ds = "2006-01-02" | ||
11 | YYYYMMDD_dt = "2006.01.02." | ||
12 | |||
13 | DDMMYYYY_sl = "02/01/2006" | ||
14 | DDMMYYYY_ds = "02-01-2006" | ||
15 | DDMMYYYY_dt = "02.01.2006." | ||
16 | |||
17 | YYYYMMDD_HHMMSS_sl = "2006/01/02 15:04:05" | ||
18 | YYYYMMDD_HHMMSS_ds = "2006-01-02 15:04:05" | ||
19 | YYYYMMDD_HHMMSS_dt = "2006.01.02. 15:04:05" | ||
20 | |||
21 | DDMMYYYY_HHMMSS_sl = "02/01/2006 15:04:05" | ||
22 | DDMMYYYY_HHMMSS_ds = "02-01-2006 15:04:05" | ||
23 | DDMMYYYY_HHMMSS_dt = "02.01.2006. 15:04:05" | ||
24 | ) | ||
25 | |||
26 | func Systime() int64 { | ||
27 | return time.Now().Unix() | ||
28 | } | ||
29 | |||
30 | func DateToEpoch(date, format string) int64 { | ||
31 | t, err := time.Parse(format, date) | ||
32 | if err != nil { | ||
33 | fmt.Println(err.Error()) | ||
34 | return 0 | ||
35 | } | ||
36 | return t.Unix() | ||
37 | } | ||
38 | |||
39 | func EpochToDate(e int64, format string) string { | ||
40 | return time.Unix(e, 0).Format(format) | ||
41 | } | ||
42 |
filtering.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | ||
5 | "net/http" | 4 | "net/http" |
6 | "net/url" | 5 | "net/url" |
7 | "strings" | 6 | "strings" |
8 | ) | 7 | ) |
9 | 8 | ||
10 | // Filter ... | 9 | // Filter ... |
11 | type Filter map[string][]string | 10 | type Filter map[string][]string |
12 | 11 | ||
13 | // Get ... | 12 | // Get ... |
14 | func (f Filter) Get(key string) (values []string, ok bool) { | 13 | func (f Filter) Get(key string) (values []string, ok bool) { |
15 | values, ok = f[key] | 14 | values, ok = f[key] |
16 | return values, ok | 15 | return values, ok |
17 | } | 16 | } |
18 | 17 | ||
19 | // Count ... | 18 | // Count ... |
20 | func (f Filter) Count() int { | 19 | func (f Filter) Count() int { |
21 | return len(f) | 20 | return len(f) |
22 | } | 21 | } |
23 | 22 | ||
24 | // Add ... | 23 | // Add ... |
25 | func (f Filter) Add(key, val string) { | 24 | func (f Filter) Add(key, val string) { |
26 | f[key] = append(f[key], val) | 25 | f[key] = append(f[key], val) |
27 | } | 26 | } |
28 | 27 | ||
29 | // ValueAt ... | 28 | // ValueAt ... |
30 | func (f Filter) ValueAt(val string, index int) string { | 29 | func (f Filter) ValueAt(val string, index int) string { |
31 | if filter, ok := f[val]; ok { | 30 | if filter, ok := f[val]; ok { |
32 | if len(filter) > index { | 31 | if len(filter) > index { |
33 | return filter[index] | 32 | return filter[index] |
34 | } | 33 | } |
35 | } | 34 | } |
36 | 35 | ||
37 | return "" | 36 | return "" |
38 | } | 37 | } |
39 | 38 | ||
40 | func (f Filter) validate(validFilters []string) (Filter, bool) { | 39 | func (f Filter) Validate(validFilters []string) (Filter, bool) { |
41 | goodFilters := make(Filter) | 40 | goodFilters := make(Filter) |
42 | cnt, len := 0, 0 | 41 | cnt, len := 0, 0 |
43 | for fi := range f { | 42 | for fi := range f { |
44 | len++ | 43 | len++ |
45 | for _, v := range validFilters { | 44 | for _, v := range validFilters { |
46 | if fi == v { | 45 | if fi == v { |
47 | cnt++ | 46 | cnt++ |
48 | goodFilters[fi] = f[fi] | 47 | goodFilters[fi] = f[fi] |
49 | } | 48 | } |
50 | } | 49 | } |
51 | } | 50 | } |
52 | 51 | ||
53 | result := true | 52 | result := true |
54 | if len > 0 && cnt == 0 { | 53 | if len > 0 && cnt == 0 { |
55 | // if no valid filters are found declare filtering request as invalid | 54 | // if no valid filters are found declare filtering request as invalid |
56 | result = false | 55 | result = false |
57 | } | 56 | } |
58 | 57 | ||
59 | return goodFilters, result | 58 | return goodFilters, result |
60 | } | 59 | } |
61 | 60 | ||
62 | // ParseFilters requires input in format: "param1::value1|param2::value2..." | 61 | // ParseFilters requires input in format: "param1::value1|param2::value2..." |
63 | func ParseFilters(req *http.Request, header string) (filters Filter) { | 62 | func ParseFilters(req *http.Request, header string) (filters Filter) { |
64 | q := req.FormValue(header) | 63 | q := req.FormValue(header) |
65 | q = strings.Trim(q, "\"") | 64 | q = strings.Trim(q, "\"") |
66 | kvp := strings.Split(q, "|") | 65 | kvp := strings.Split(q, "|") |
67 | filters = make(Filter, len(kvp)) | 66 | filters = make(Filter, len(kvp)) |
68 | 67 | ||
69 | for i := range kvp { | 68 | for i := range kvp { |
70 | kv := strings.Split(kvp[i], "::") | 69 | kv := strings.Split(kvp[i], "::") |
71 | if len(kv) == 2 { | 70 | if len(kv) == 2 { |
72 | key, _ := url.QueryUnescape(kv[0]) | 71 | key, _ := url.QueryUnescape(kv[0]) |
73 | 72 | ||
74 | // get values (if more than 1) | 73 | // get values (if more than 1) |
75 | vals := strings.Split(kv[1], ",") | 74 | vals := strings.Split(kv[1], ",") |
76 | for _, v := range vals { | 75 | for _, v := range vals { |
77 | u, _ := url.QueryUnescape(v) | 76 | u, _ := url.QueryUnescape(v) |
78 | filters[key] = append(filters[key], u) | 77 | filters[key] = append(filters[key], u) |
79 | } | 78 | } |
80 | } | 79 | } |
81 | } | 80 | } |
82 | 81 | ||
83 | return filters | 82 | return filters |
84 | } | 83 | } |
85 | |||
86 | // MakeFilterString is very dodgy, needs more robustness. | ||
87 | // TODO(marko) | ||
88 | func MakeFilterString(prefix string, filters Filter, validFilters []string) (res string, ok bool) { | ||
89 | if prefix != "" { | ||
90 | prefix += "." | ||
91 | } | ||
92 | |||
93 | if filters.Count() == 0 { | ||
94 | return "", true | ||
95 | } | ||
96 | |||
97 | filters, ok = filters.validate(validFilters) | ||
98 | if !ok { | ||
99 | return "", false | ||
100 | } | ||
101 | |||
102 | first := true | ||
103 | for k, filter := range filters { | ||
104 | symbol := "=" | ||
105 | |||
106 | if first { | ||
107 | res += " where " | ||
108 | first = false | ||
109 | } else { | ||
110 | res += " and " | ||
111 | } | ||
112 | |||
113 | res += "(" | ||
114 | for i, f := range filter { | ||
115 | if strings.HasPrefix(f, "<") || strings.HasPrefix(f, ">") || strings.HasPrefix(f, "!") { | ||
116 | symbol = string(f[0]) | ||
117 | f = strings.TrimLeft(f, "<>!") | ||
118 | if strings.HasPrefix(f, "=") { | ||
119 | f = strings.TrimLeft(f, "=") | ||
120 | symbol += "=" | ||
121 | } | ||
122 | } | ||
123 | |||
124 | res += fmt.Sprintf("%s%s %s '%s'", prefix, k, symbol, f) | ||
125 | |||
126 | if i < len(filter)-1 { | ||
127 | res += " or " | ||
128 | } | ||
129 | } | ||
130 | res += ")" | ||
131 | } | ||
132 | |||
133 | return res, ok | ||
134 | } | ||
135 | 84 |
float_util.go
File was created | 1 | package webutility | |
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "math" | ||
6 | "math/big" | ||
7 | ) | ||
8 | |||
9 | func RoundFloat64(f float64, dec int) float64 { | ||
10 | p := math.Pow(10, float64(dec)) | ||
11 | return math.Round(f*p) / p | ||
12 | } | ||
13 | |||
14 | func NewBF(f float64, prec uint) *big.Float { | ||
15 | x := big.NewFloat(f) | ||
16 | x.SetPrec(prec) | ||
17 | return x | ||
18 | } | ||
19 | |||
20 | func AddBF(x, y *big.Float) *big.Float { | ||
21 | z := big.NewFloat(0.0) | ||
22 | z.SetPrec(x.Prec()) | ||
23 | z.Add(x, y) | ||
24 | return z | ||
25 | } | ||
26 | |||
27 | func SubBF(x, y *big.Float) *big.Float { | ||
28 | z := big.NewFloat(0.0) | ||
29 | z.SetPrec(x.Prec()) | ||
30 | |||
31 | yneg := big.NewFloat(0.0) | ||
32 | yneg.Neg(y) | ||
33 | |||
34 | z.Add(x, yneg) | ||
35 | return z | ||
36 | } | ||
37 | |||
38 | func MulBF(x, y *big.Float) *big.Float { | ||
39 | z := big.NewFloat(0.0) | ||
40 | z.SetPrec(x.Prec()) | ||
41 | z.Mul(x, y) | ||
42 | return z | ||
43 | } | ||
44 | |||
45 | func DivBF(x, y *big.Float) *big.Float { | ||
46 | z := big.NewFloat(0.0) | ||
47 | z.SetPrec(x.Prec()) | ||
48 | z.Quo(x, y) | ||
49 | return z | ||
50 | } | ||
51 | |||
52 | func BFtoFloat(f *big.Float) float64 { | ||
53 | v, _ := f.Float64() | ||
54 | return v | ||
55 | } | ||
56 | |||
57 | func Float64ToString(f float64) string { | ||
58 | return fmt.Sprintf("%.2f", f) | ||
59 | } | ||
60 |
int_util.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | 4 | "fmt" |
5 | "strconv" | ||
6 | ) | 5 | ) |
7 | 6 | ||
8 | // ClampInt64 ... | 7 | // ClampInt64 ... |
9 | func ClampInt64(v, min, max int64) int64 { | 8 | func ClampInt64(v, min, max int64) int64 { |
10 | if v < min { | 9 | if v < min { |
11 | return min | 10 | return min |
12 | } else if v > max { | 11 | } else if v > max { |
13 | return max | 12 | return max |
14 | } | 13 | } |
15 | 14 | ||
16 | return v | 15 | return v |
17 | } | 16 | } |
18 | 17 | ||
19 | // InRangeInt64 ... | 18 | // InRangeInt64 ... |
20 | func InRangeInt64(v, min, max int64) bool { | 19 | func InRangeInt64(v, min, max int64) bool { |
21 | return (v >= min && v <= max) | 20 | return (v >= min && v <= max) |
22 | } | 21 | } |
23 | 22 | ||
24 | // StringToInt64 ... | ||
25 | func StringToInt64(s string) int64 { | ||
26 | i, _ := strconv.ParseInt(s, 10, 64) | ||
27 | return i | ||
28 | } | ||
29 | |||
30 | // StringToFloat64 ... | ||
31 | func StringToFloat64(s string) float64 { | ||
32 | f, _ := strconv.ParseFloat(s, 64) | ||
33 | return f | ||
34 | } | ||
35 | |||
36 | // Int64ToString ... | 23 | // Int64ToString ... |
37 | func Int64ToString(i int64) string { | 24 | func Int64ToString(i int64) string { |
38 | return fmt.Sprintf("%d", i) | 25 | return fmt.Sprintf("%d", i) |
39 | } | 26 | } |
40 | 27 | ||
41 | // BoolToInt64 ... | 28 | // BoolToInt64 ... |
42 | func BoolToInt64(b bool) int64 { | 29 | func BoolToInt64(b bool) int64 { |
43 | if b { | 30 | if b { |
44 | return 1 | 31 | return 1 |
45 | } | 32 | } |
46 | return 0 | 33 | return 0 |
47 | } | 34 | } |
48 | 35 | ||
49 | // Int64ToBool ... | 36 | // Int64ToBool ... |
50 | func Int64ToBool(i int64) bool { | 37 | func Int64ToBool(i int64) bool { |
51 | return i != 0 | 38 | return i != 0 |
52 | } | 39 | } |
53 | 40 | ||
54 | func StringToValidInt64(s string) (int64, bool) { | 41 | func MaxInt(vars ...int) (max int) { |
55 | i, err := strconv.ParseInt(s, 10, 64) | 42 | for _, v := range vars { |
56 | if err != nil { | 43 | if v > max { |
57 | return i, false | 44 | max = v |
45 | } | ||
58 | } | 46 | } |
59 | return i, true | 47 | return max |
60 | } | 48 | } |
middleware/main.go
1 | package middleware | 1 | package middleware |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | 4 | "fmt" |
5 | "io/ioutil" | 5 | "io/ioutil" |
6 | "net/http" | 6 | "net/http" |
7 | "os" | ||
8 | "strings" | ||
7 | 9 | ||
8 | web "git.to-net.rs/marko.tikvic/webutility" | 10 | web "git.to-net.rs/marko.tikvic/webutility" |
9 | ) | 11 | ) |
10 | 12 | ||
11 | func Headers(h http.HandlerFunc) http.HandlerFunc { | 13 | func Headers(h http.HandlerFunc) http.HandlerFunc { |
12 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h))) | 14 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h))) |
13 | } | 15 | } |
14 | 16 | ||
15 | func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc { | 17 | func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc { |
16 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h)))) | 18 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h)))) |
17 | } | 19 | } |
18 | 20 | ||
19 | func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc { | 21 | func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc { |
20 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h))))) | 22 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h))))) |
21 | } | 23 | } |
22 | 24 | ||
23 | func LogTraffic(h http.HandlerFunc) http.HandlerFunc { | 25 | func LogTraffic(h http.HandlerFunc) http.HandlerFunc { |
24 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h)))) | 26 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h)))) |
25 | } | 27 | } |
26 | 28 | ||
27 | func TrafficLogsHandler(w http.ResponseWriter, req *http.Request) { | 29 | func TrafficLogsHandler(w http.ResponseWriter, req *http.Request) { |
28 | switch req.Method { | 30 | switch req.Method { |
29 | case "GET": | 31 | case "GET": |
30 | files, err := ioutil.ReadDir(httpLogger.GetOutDir() + "/") | 32 | logfile := req.FormValue("logfile") |
31 | if err != nil { | 33 | if logfile == "" { |
32 | web.InternalServerError(w, req, err.Error()) | 34 | files, err := ioutil.ReadDir(httpLogger.GetOutDir() + "/") |
33 | return | 35 | if err != nil { |
34 | } | 36 | web.InternalServerError(w, req, err.Error()) |
37 | return | ||
38 | } | ||
35 | 39 | ||
36 | web.SetContentType(w, "text/html; charset=utf-8") | 40 | errorLogs := make([]os.FileInfo, 0) |
37 | web.SetResponseStatus(w, http.StatusOK) | 41 | httpLogs := make([]os.FileInfo, 0) |
38 | |||
39 | web.WriteResponse(w, []byte("<body style='background-color: black; color: white'>")) | ||
40 | inputForm := ` | ||
41 | <div> | ||
42 | <form action="/api/v1/logs" method="POST" target="_blank"> | ||
43 | Username:<br> | ||
44 | <input type="text" name="username"><br> | ||
45 | Password:<br> | ||
46 | <input type="password" name="password"><br> | ||
47 | Log:<br> | ||
48 | <input type="text" name="logfile"><br> | ||
49 | <input type="submit" value="View"> | ||
50 | </form> | ||
51 | </div>` | ||
52 | web.WriteResponse(w, []byte(inputForm)) | ||
53 | |||
54 | web.WriteResponse(w, []byte("<table>")) | ||
55 | web.WriteResponse(w, []byte("<tr><th>Name</th><th>Size</th></tr>")) | ||
56 | for i := range files { | ||
57 | name := files[i].Name() | ||
58 | size := files[i].Size() | ||
59 | div := fmt.Sprintf(`<tr><td>%s</td><td style="text-align:right">%dB</td></tr>`, name, size) | ||
60 | web.WriteResponse(w, []byte(div)) | ||
61 | } | ||
62 | web.WriteResponse(w, []byte("</table></body>")) | ||
63 | 42 | ||
64 | case "POST": | 43 | var errorLogsCount, httpLogsCount int |
65 | web.SetContentType(w, "text/html; charset=utf-8") | 44 | for _, f := range files { |
45 | if strings.HasPrefix(f.Name(), "err") { | ||
46 | errorLogs = append(errorLogs, f) | ||
47 | errorLogsCount++ | ||
48 | } else if strings.HasPrefix(f.Name(), "http") { | ||
49 | httpLogs = append(httpLogs, f) | ||
50 | httpLogsCount++ | ||
51 | } | ||
52 | } | ||
66 | 53 | ||
67 | logfile := req.FormValue("logfile") | 54 | web.SetContentType(w, "text/html; charset=utf-8") |
68 | content, err := web.ReadFileContent(httpLogger.GetOutDir() + "/" + logfile) | 55 | web.SetResponseStatus(w, http.StatusOK) |
69 | if err != nil { | 56 | |
70 | web.InternalServerError(w, req, err.Error()) | 57 | web.WriteResponse(w, []byte(` |
71 | return | 58 | <body style='background-color: black; color: white'> |
59 | <table> | ||
60 | <tr> | ||
61 | <th>Error logs</th><th></th> <th style="width: 25px"></th> | ||
62 | <th>Traffic logs</th><th></th> | ||
63 | </tr> | ||
64 | `)) | ||
65 | |||
66 | var ( | ||
67 | div, name string | ||
68 | size int64 | ||
69 | ) | ||
70 | |||
71 | max := errorLogsCount | ||
72 | if httpLogsCount > errorLogsCount { | ||
73 | max = httpLogsCount | ||
74 | } | ||
75 | |||
76 | for i := 0; i < max; i++ { | ||
77 | div = "<tr>" | ||
78 | |||
79 | if i < errorLogsCount { | ||
80 | name = errorLogs[i].Name() | ||
81 | size = errorLogs[i].Size() | ||
82 | div += fmt.Sprintf(` | ||
83 | <td> | ||
84 | <a style="color: white" | ||
85 | href="/api/v1/logs?logfile=%s" | ||
86 | target="_blank">%s | ||
87 | </a> | ||
88 | </td> | ||
89 | <td style="color: white; text-align:right">%dB</td>`, | ||
90 | name, name, size, | ||
91 | ) | ||
92 | } else { | ||
93 | div += fmt.Sprintf(`<td></td><td></td>`) | ||
94 | } | ||
95 | |||
96 | div += "<td></td>" | ||
97 | |||
98 | if i < httpLogsCount { | ||
99 | name := httpLogs[i].Name() | ||
100 | size := httpLogs[i].Size() | ||
101 | div += fmt.Sprintf(` | ||
102 | <td> | ||
103 | <a style="color: white" | ||
104 | href="/api/v1/logs?logfile=%s" | ||
105 | target="_blank">%s | ||
106 | </a></td> | ||
107 | <td style="color: white; text-align:right">%dB</td>`, |
nullables.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "database/sql" | 4 | "database/sql" |
5 | "database/sql/driver" | 5 | "database/sql/driver" |
6 | "encoding/json" | 6 | "encoding/json" |
7 | "fmt" | 7 | "fmt" |
8 | "time" | 8 | "time" |
9 | ) | 9 | ) |
10 | 10 | ||
11 | // NullBool is a wrapper for sql.NullBool with added JSON (un)marshalling | 11 | // NullBool is a wrapper for sql.NullBool with added JSON (un)marshalling |
12 | type NullBool sql.NullBool | 12 | type NullBool sql.NullBool |
13 | 13 | ||
14 | // Scan ... | 14 | // Scan ... |
15 | func (nb *NullBool) Scan(value interface{}) error { | 15 | func (nb *NullBool) Scan(value interface{}) error { |
16 | var b sql.NullBool | 16 | var b sql.NullBool |
17 | if err := b.Scan(value); err != nil { | 17 | if err := b.Scan(value); err != nil { |
18 | nb.Bool, nb.Valid = false, false | 18 | nb.Bool, nb.Valid = false, false |
19 | return err | 19 | return err |
20 | } | 20 | } |
21 | nb.Bool, nb.Valid = b.Bool, b.Valid | 21 | nb.Bool, nb.Valid = b.Bool, b.Valid |
22 | return nil | 22 | return nil |
23 | } | 23 | } |
24 | 24 | ||
25 | // Value ... | 25 | // Value ... |
26 | func (nb *NullBool) Value() (driver.Value, error) { | 26 | func (nb *NullBool) Value() (driver.Value, error) { |
27 | if !nb.Valid { | 27 | if !nb.Valid { |
28 | return nil, nil | 28 | return nil, nil |
29 | } | 29 | } |
30 | return nb.Bool, nil | 30 | return nb.Bool, nil |
31 | } | 31 | } |
32 | 32 | ||
33 | // MarshalJSON ... | 33 | // MarshalJSON ... |
34 | func (nb NullBool) MarshalJSON() ([]byte, error) { | 34 | func (nb NullBool) MarshalJSON() ([]byte, error) { |
35 | if nb.Valid { | 35 | if nb.Valid { |
36 | return json.Marshal(nb.Bool) | 36 | return json.Marshal(nb.Bool) |
37 | } | 37 | } |
38 | 38 | ||
39 | return json.Marshal(nil) | 39 | return json.Marshal(nil) |
40 | } | 40 | } |
41 | 41 | ||
42 | // UnmarshalJSON ... | 42 | // UnmarshalJSON ... |
43 | func (nb *NullBool) UnmarshalJSON(b []byte) error { | 43 | func (nb *NullBool) UnmarshalJSON(b []byte) error { |
44 | var temp *bool | 44 | var temp *bool |
45 | if err := json.Unmarshal(b, &temp); err != nil { | 45 | if err := json.Unmarshal(b, &temp); err != nil { |
46 | return err | 46 | return err |
47 | } | 47 | } |
48 | if temp != nil { | 48 | if temp != nil { |
49 | nb.Valid = true | 49 | nb.Valid = true |
50 | nb.Bool = *temp | 50 | nb.Bool = *temp |
51 | } else { | 51 | } else { |
52 | nb.Valid = false | 52 | nb.Valid = false |
53 | } | 53 | } |
54 | return nil | 54 | return nil |
55 | } | 55 | } |
56 | 56 | ||
57 | // SQLCast ... | 57 | // SQLCast ... |
58 | func (nb *NullBool) SQLCast() sql.NullBool { | 58 | func (nb *NullBool) SQLCast() sql.NullBool { |
59 | return sql.NullBool(*nb) | 59 | return sql.NullBool(*nb) |
60 | } | 60 | } |
61 | 61 | ||
62 | // NullString is a wrapper for sql.NullString with added JSON (un)marshalling | 62 | // NullString is a wrapper for sql.NullString with added JSON (un)marshalling |
63 | type NullString sql.NullString | 63 | type NullString sql.NullString |
64 | 64 | ||
65 | // Scan ... | 65 | // Scan ... |
66 | func (ns *NullString) Scan(value interface{}) error { | 66 | func (ns *NullString) Scan(value interface{}) error { |
67 | var s sql.NullString | 67 | var s sql.NullString |
68 | if err := s.Scan(value); err != nil { | 68 | if err := s.Scan(value); err != nil { |
69 | ns.String, ns.Valid = "", false | 69 | ns.String, ns.Valid = "", false |
70 | return err | 70 | return err |
71 | } | 71 | } |
72 | ns.String, ns.Valid = s.String, s.Valid | 72 | ns.String, ns.Valid = s.String, s.Valid |
73 | return nil | 73 | return nil |
74 | } | 74 | } |
75 | 75 | ||
76 | // Value ... | 76 | // Value ... |
77 | func (ns *NullString) Value() (driver.Value, error) { | 77 | func (ns *NullString) Value() (driver.Value, error) { |
78 | if !ns.Valid { | 78 | if !ns.Valid { |
79 | return nil, nil | 79 | return nil, nil |
80 | } | 80 | } |
81 | return ns.String, nil | 81 | return ns.String, nil |
82 | } | 82 | } |
83 | 83 | ||
84 | // MarshalJSON ... | 84 | // MarshalJSON ... |
85 | func (ns NullString) MarshalJSON() ([]byte, error) { | 85 | func (ns NullString) MarshalJSON() ([]byte, error) { |
86 | if ns.Valid { | 86 | if ns.Valid { |
87 | return json.Marshal(ns.String) | 87 | return json.Marshal(ns.String) |
88 | } | 88 | } |
89 | return json.Marshal(nil) | 89 | return json.Marshal(nil) |
90 | } | 90 | } |
91 | 91 | ||
92 | // UnmarshalJSON ... | 92 | // UnmarshalJSON ... |
93 | func (ns *NullString) UnmarshalJSON(b []byte) error { | 93 | func (ns *NullString) UnmarshalJSON(b []byte) error { |
94 | var temp *string | 94 | var temp *string |
95 | if err := json.Unmarshal(b, &temp); err != nil { | 95 | if err := json.Unmarshal(b, &temp); err != nil { |
96 | return err | 96 | return err |
97 | } | 97 | } |
98 | if temp != nil { | 98 | if temp != nil { |
99 | ns.Valid = true | 99 | ns.Valid = true |
100 | ns.String = *temp | 100 | ns.String = *temp |
101 | } else { | 101 | } else { |
102 | ns.Valid = false | 102 | ns.Valid = false |
103 | } | 103 | } |
104 | return nil | 104 | return nil |
105 | } | 105 | } |
106 | 106 | ||
107 | // SQLCast ... | 107 | // SQLCast ... |
108 | func (ns *NullString) SQLCast() sql.NullString { | 108 | func (ns *NullString) SQLCast() sql.NullString { |
109 | return sql.NullString(*ns) | 109 | return sql.NullString(*ns) |
110 | } | 110 | } |
111 | 111 | ||
112 | // NullInt64 is a wrapper for sql.NullInt64 with added JSON (un)marshalling | 112 | // NullInt64 is a wrapper for sql.NullInt64 with added JSON (un)marshalling |
113 | type NullInt64 sql.NullInt64 | 113 | type NullInt64 sql.NullInt64 |
114 | 114 | ||
115 | // Scan ... | 115 | // Scan ... |
116 | func (ni *NullInt64) Scan(value interface{}) error { | 116 | func (ni *NullInt64) Scan(value interface{}) error { |
117 | var i sql.NullInt64 | 117 | var i sql.NullInt64 |
118 | if err := i.Scan(value); err != nil { | 118 | if err := i.Scan(value); err != nil { |
119 | ni.Int64, ni.Valid = 0, false | 119 | ni.Int64, ni.Valid = 0, false |
120 | return err | 120 | return err |
121 | } | 121 | } |
122 | ni.Int64, ni.Valid = i.Int64, i.Valid | 122 | ni.Int64, ni.Valid = i.Int64, i.Valid |
123 | return nil | 123 | return nil |
124 | } | 124 | } |
125 | 125 | ||
126 | // ScanPtr ... | ||
127 | func (ni *NullInt64) ScanPtr(v interface{}) error { | ||
128 | if ip, ok := v.(*int64); ok && ip != nil { | ||
129 | return ni.Scan(*ip) | ||
130 | } | ||
131 | return nil | ||
132 | } | ||
133 | |||
126 | // Value ... | 134 | // Value ... |
127 | func (ni *NullInt64) Value() (driver.Value, error) { | 135 | func (ni *NullInt64) Value() (driver.Value, error) { |
128 | if !ni.Valid { | 136 | if !ni.Valid { |
129 | return nil, nil | 137 | return nil, nil |
130 | } | 138 | } |
131 | return ni.Int64, nil | 139 | return ni.Int64, nil |
132 | } | 140 | } |
133 | 141 | ||
134 | // MarshalJSON ... | 142 | // MarshalJSON ... |
135 | func (ni NullInt64) MarshalJSON() ([]byte, error) { | 143 | func (ni NullInt64) MarshalJSON() ([]byte, error) { |
136 | if ni.Valid { | 144 | if ni.Valid { |
137 | return json.Marshal(ni.Int64) | 145 | return json.Marshal(ni.Int64) |
138 | } | 146 | } |
139 | return json.Marshal(nil) | 147 | return json.Marshal(nil) |
140 | } | 148 | } |
141 | 149 | ||
142 | // UnmarshalJSON ... | 150 | // UnmarshalJSON ... |
143 | func (ni *NullInt64) UnmarshalJSON(b []byte) error { | 151 | func (ni *NullInt64) UnmarshalJSON(b []byte) error { |
144 | var temp *int64 | 152 | var temp *int64 |
145 | if err := json.Unmarshal(b, &temp); err != nil { | 153 | if err := json.Unmarshal(b, &temp); err != nil { |
146 | return err | 154 | return err |
147 | } | 155 | } |
148 | if temp != nil { | 156 | if temp != nil { |
149 | ni.Valid = true | 157 | ni.Valid = true |
150 | ni.Int64 = *temp | 158 | ni.Int64 = *temp |
151 | } else { | 159 | } else { |
152 | ni.Valid = false | 160 | ni.Valid = false |
153 | } | 161 | } |
154 | return nil | 162 | return nil |
155 | } | 163 | } |
156 | 164 | ||
157 | // SQLCast ... | 165 | // SQLCast ... |
158 | func (ni *NullInt64) SQLCast() sql.NullInt64 { | 166 | func (ni *NullInt64) SQLCast() sql.NullInt64 { |
159 | return sql.NullInt64(*ni) | 167 | return sql.NullInt64(*ni) |
160 | } | 168 | } |
161 | 169 | ||
162 | // NullFloat64 is a wrapper for sql.NullFloat64 with added JSON (un)marshalling | 170 | // NullFloat64 is a wrapper for sql.NullFloat64 with added JSON (un)marshalling |
163 | type NullFloat64 sql.NullFloat64 | 171 | type NullFloat64 sql.NullFloat64 |
164 | 172 | ||
165 | // Scan ... | 173 | // Scan ... |
166 | func (nf *NullFloat64) Scan(value interface{}) error { | 174 | func (nf *NullFloat64) Scan(value interface{}) error { |
167 | var f sql.NullFloat64 | 175 | var f sql.NullFloat64 |
168 | if err := f.Scan(value); err != nil { | 176 | if err := f.Scan(value); err != nil { |
169 | nf.Float64, nf.Valid = 0.0, false | 177 | nf.Float64, nf.Valid = 0.0, false |
170 | return err | 178 | return err |
171 | } | 179 | } |
172 | nf.Float64, nf.Valid = f.Float64, f.Valid | 180 | nf.Float64, nf.Valid = f.Float64, f.Valid |
173 | return nil | 181 | return nil |
174 | } | 182 | } |
175 | 183 | ||
184 | // ScanPtr ... | ||
185 | func (nf *NullFloat64) ScanPtr(v interface{}) error { | ||
186 | if fp, ok := v.(*float64); ok && fp != nil { | ||
187 | return nf.Scan(*fp) | ||
188 | } | ||
189 | return nil | ||
190 | } | ||
191 | |||
176 | // Value ... | 192 | // Value ... |
177 | func (nf *NullFloat64) Value() (driver.Value, error) { | 193 | func (nf *NullFloat64) Value() (driver.Value, error) { |
178 | if !nf.Valid { | 194 | if !nf.Valid { |
179 | return nil, nil | 195 | return nil, nil |
180 | } | 196 | } |
181 | return nf.Float64, nil | 197 | return nf.Float64, nil |
182 | } | 198 | } |
183 | 199 | ||
184 | // MarshalJSON ... | 200 | // MarshalJSON ... |
185 | func (nf NullFloat64) MarshalJSON() ([]byte, error) { | 201 | func (nf NullFloat64) MarshalJSON() ([]byte, error) { |
186 | if nf.Valid { | 202 | if nf.Valid { |
187 | return json.Marshal(nf.Float64) | 203 | return json.Marshal(nf.Float64) |
188 | } | 204 | } |
189 | return json.Marshal(nil) | 205 | return json.Marshal(nil) |
190 | } | 206 | } |
191 | 207 | ||
192 | // UnmarshalJSON ... | 208 | // UnmarshalJSON ... |
193 | func (nf *NullFloat64) UnmarshalJSON(b []byte) error { | 209 | func (nf *NullFloat64) UnmarshalJSON(b []byte) error { |
194 | var temp *float64 | 210 | var temp *float64 |
195 | if err := json.Unmarshal(b, &temp); err != nil { | 211 | if err := json.Unmarshal(b, &temp); err != nil { |
196 | return err | 212 | return err |
197 | } | 213 | } |
198 | if temp != nil { | 214 | if temp != nil { |
199 | nf.Valid = true | 215 | nf.Valid = true |
200 | nf.Float64 = *temp | 216 | nf.Float64 = *temp |
201 | } else { | 217 | } else { |
202 | nf.Valid = false | 218 | nf.Valid = false |
203 | } | 219 | } |
204 | return nil | 220 | return nil |
205 | } | 221 | } |
206 | 222 | ||
207 | // SQLCast ... | 223 | // SQLCast ... |
208 | func (nf *NullFloat64) SQLCast() sql.NullFloat64 { | 224 | func (nf *NullFloat64) SQLCast() sql.NullFloat64 { |
209 | return sql.NullFloat64(*nf) | 225 | return sql.NullFloat64(*nf) |
210 | } | 226 | } |
211 | 227 | ||
212 | // NullTime ... | 228 | // NullTime ... |
213 | type NullTime struct { | 229 | type NullTime struct { |
214 | Time time.Time | 230 | Time time.Time |
215 | Valid bool // Valid is true if Time is not NULL | 231 | Valid bool // Valid is true if Time is not NULL |
216 | } | 232 | } |
217 | 233 | ||
218 | // Scan ... | 234 | // Scan ... |
219 | func (nt *NullTime) Scan(value interface{}) (err error) { | 235 | func (nt *NullTime) Scan(value interface{}) (err error) { |
220 | if value == nil { | 236 | if value == nil { |
221 | nt.Time, nt.Valid = time.Time{}, false | 237 | nt.Time, nt.Valid = time.Time{}, false |
222 | return | 238 | return |
223 | } | 239 | } |
224 | 240 | ||
225 | switch v := value.(type) { | 241 | switch v := value.(type) { |
226 | case time.Time: | 242 | case time.Time: |
227 | nt.Time, nt.Valid = v, true | 243 | nt.Time, nt.Valid = v, true |
228 | return | 244 | return |
229 | case []byte: | 245 | case []byte: |
230 | nt.Time, err = parseDateTime(string(v), time.UTC) | 246 | nt.Time, err = parseDateTime(string(v), time.UTC) |
231 | nt.Valid = (err == nil) | 247 | nt.Valid = (err == nil) |
232 | return | 248 | return |
233 | case string: | 249 | case string: |
234 | nt.Time, err = parseDateTime(v, time.UTC) | 250 | nt.Time, err = parseDateTime(v, time.UTC) |
235 | nt.Valid = (err == nil) | 251 | nt.Valid = (err == nil) |
236 | return | 252 | return |
237 | } | 253 | } |
238 | 254 | ||
239 | nt.Valid = false | 255 | nt.Valid = false |
240 | return fmt.Errorf("Can't convert %T to time.Time", value) | 256 | return fmt.Errorf("Can't convert %T to time.Time", value) |
241 | } | 257 | } |
242 | 258 | ||
243 | // Value implements the driver Valuer interface. | 259 | // Value implements the driver Valuer interface. |
244 | func (nt NullTime) Value() (driver.Value, error) { | 260 | func (nt NullTime) Value() (driver.Value, error) { |
245 | if !nt.Valid { | 261 | if !nt.Valid { |
246 | return nil, nil | 262 | return nil, nil |
247 | } | 263 | } |
248 | return nt.Time, nil | 264 | return nt.Time, nil |
249 | } | 265 | } |
250 | 266 | ||
251 | // MarshalJSON ... | 267 | // MarshalJSON ... |
252 | func (nt NullTime) MarshalJSON() ([]byte, error) { | 268 | func (nt NullTime) MarshalJSON() ([]byte, error) { |
253 | if nt.Valid { | 269 | if nt.Valid { |
254 | format := nt.Time.Format("2006-01-02 15:04:05") | 270 | format := nt.Time.Format("2006-01-02 15:04:05") |
255 | return json.Marshal(format) | 271 | return json.Marshal(format) |
256 | } | 272 | } |
257 | return json.Marshal(nil) | 273 | return json.Marshal(nil) |
258 | } | 274 | } |
259 | 275 | ||
260 | // UnmarshalJSON ... | 276 | // UnmarshalJSON ... |
261 | func (nt *NullTime) UnmarshalJSON(b []byte) error { | 277 | func (nt *NullTime) UnmarshalJSON(b []byte) error { |
262 | var temp *time.Time | 278 | var temp *time.Time |
263 | var t1 time.Time | 279 | var t1 time.Time |
264 | var err error | 280 | var err error |
265 | 281 | ||
266 | s1 := string(b) | 282 | s1 := string(b) |
267 | s2 := s1[1 : len(s1)-1] | 283 | s2 := s1[1 : len(s1)-1] |
268 | if s1 == "null" { | 284 | if s1 == "null" { |
269 | temp = nil | 285 | temp = nil |
270 | } else { | 286 | } else { |
271 | t1, err = time.Parse("2006-01-02 15:04:05", s2) | 287 | t1, err = time.Parse("2006-01-02 15:04:05", s2) |
272 | if err != nil { | 288 | if err != nil { |
273 | return err | 289 | return err |
274 | } | 290 | } |
275 | temp = &t1 | 291 | temp = &t1 |
276 | } | 292 | } |
277 | 293 | ||
278 | if temp != nil { | 294 | if temp != nil { |
279 | nt.Valid = true | 295 | nt.Valid = true |
280 | nt.Time = *temp | 296 | nt.Time = *temp |
281 | } else { | 297 | } else { |
282 | nt.Valid = false | 298 | nt.Valid = false |
283 | } | 299 | } |
284 | return nil | 300 | return nil |
285 | } | 301 | } |
286 | 302 | ||
287 | func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { | 303 | func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { |
288 | base := "0000-00-00 00:00:00.0000000" | 304 | base := "0000-00-00 00:00:00.0000000" |
289 | timeFormat := "2006-01-02 15:04:05.999999" | 305 | timeFormat := "2006-01-02 15:04:05.999999" |
290 | switch len(str) { | 306 | switch len(str) { |
291 | case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" | 307 | case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" |
292 | if str == base[:len(str)] { | 308 | if str == base[:len(str)] { |
293 | return | 309 | return |
294 | } | 310 | } |
295 | t, err = time.Parse(timeFormat[:len(str)], str) | 311 | t, err = time.Parse(timeFormat[:len(str)], str) |
296 | default: | 312 | default: |
297 | err = fmt.Errorf("invalid time string: %s", str) | 313 | err = fmt.Errorf("invalid time string: %s", str) |
298 | return | 314 | return |
299 | } | 315 | } |
300 | 316 | ||
301 | // Adjust location | 317 | // Adjust location |
302 | if err == nil && loc != time.UTC { | 318 | if err == nil && loc != time.UTC { |
303 | y, mo, d := t.Date() | 319 | y, mo, d := t.Date() |
304 | h, mi, s := t.Clock() | 320 | h, mi, s := t.Clock() |
305 | t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil | 321 | t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil |
306 | } | 322 | } |
307 | 323 | ||
308 | return | 324 | return |
309 | } | 325 | } |
310 | 326 |
payload.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "database/sql" | 4 | "database/sql" |
5 | "encoding/json" | 5 | "encoding/json" |
6 | "errors" | 6 | "errors" |
7 | "fmt" | 7 | "fmt" |
8 | "net/http" | 8 | "net/http" |
9 | "strings" | 9 | "strings" |
10 | "sync" | 10 | "sync" |
11 | "time" | 11 | "time" |
12 | ) | 12 | ) |
13 | 13 | ||
14 | var ( | 14 | var ( |
15 | mu = &sync.Mutex{} | 15 | mu = &sync.Mutex{} |
16 | metadata = make(map[string]Payload) | 16 | metadata = make(map[string]Payload) |
17 | 17 | ||
18 | updateQue = make(map[string][]byte) | 18 | updateQue = make(map[string][]byte) |
19 | 19 | ||
20 | metadataDB *sql.DB | 20 | metadataDB *sql.DB |
21 | activeProject string | 21 | activeProject string |
22 | 22 | ||
23 | inited bool | 23 | inited bool |
24 | metaDriver string | 24 | metaDriver string |
25 | ) | 25 | ) |
26 | 26 | ||
27 | // LangMap ... | 27 | // LangMap ... |
28 | type LangMap map[string]map[string]string | 28 | type LangMap map[string]map[string]string |
29 | 29 | ||
30 | // Field ... | 30 | // Field ... |
31 | type Field struct { | 31 | type Field struct { |
32 | Parameter string `json:"param"` | 32 | Parameter string `json:"param"` |
33 | Type string `json:"type"` | 33 | Type string `json:"type"` |
34 | Visible bool `json:"visible"` | 34 | Visible bool `json:"visible"` |
35 | Editable bool `json:"editable"` | 35 | Editable bool `json:"editable"` |
36 | } | 36 | } |
37 | 37 | ||
38 | // CorrelationField ... | 38 | // CorrelationField ... |
39 | type CorrelationField struct { | 39 | type CorrelationField struct { |
40 | Result string `json:"result"` | 40 | Result string `json:"result"` |
41 | Elements []string `json:"elements"` | 41 | Elements []string `json:"elements"` |
42 | Type string `json:"type"` | 42 | Type string `json:"type"` |
43 | } | 43 | } |
44 | 44 | ||
45 | // Translation ... | 45 | // Translation ... |
46 | type Translation struct { | 46 | type Translation struct { |
47 | Language string `json:"language"` | 47 | Language string `json:"language"` |
48 | FieldsLabels map[string]string `json:"fieldsLabels"` | 48 | FieldsLabels map[string]string `json:"fieldsLabels"` |
49 | } | 49 | } |
50 | 50 | ||
51 | // Payload ... | 51 | // Payload ... |
52 | type Payload struct { | 52 | type Payload struct { |
53 | Method string `json:"method"` | 53 | Method string `json:"method"` |
54 | Params map[string]string `json:"params"` | 54 | Params map[string]string `json:"params,omitempty"` |
55 | Lang []Translation `json:"lang"` | 55 | Lang []Translation `json:"lang,omitempty"` |
56 | Fields []Field `json:"fields"` | 56 | Fields []Field `json:"fields,omitempty"` |
57 | Correlations []CorrelationField `json:"correlationFields"` | 57 | Correlations []CorrelationField `json:"correlationFields,omitempty"` |
58 | IDField string `json:"idField"` | 58 | IDField string `json:"idField,omitempty"` |
59 | 59 | ||
60 | Links PaginationLinks `json:"_links"` | 60 | Links PaginationLinks `json:"_links,omitempty"` |
61 | 61 | ||
62 | // Data holds JSON payload. It can't be used for itteration. | 62 | // Data holds JSON payload. It can't be used for itteration. |
63 | Data interface{} `json:"data"` | 63 | Data interface{} `json:"data"` |
64 | } | 64 | } |
65 | 65 | ||
66 | // NewPayload returs a payload sceleton for entity described with key. | 66 | // NewPayload returs a payload sceleton for entity described with key. |
67 | func NewPayload(r *http.Request, key string) Payload { | 67 | func NewPayload(r *http.Request, key string) Payload { |
68 | p := metadata[key] | 68 | p := metadata[key] |
69 | p.Method = r.Method + " " + r.RequestURI | 69 | p.Method = r.Method + " " + r.RequestURI |
70 | return p | 70 | return p |
71 | } | 71 | } |
72 | 72 | ||
73 | func (p *Payload) addLang(code string, labels map[string]string) { | 73 | func (p *Payload) addLang(code string, labels map[string]string) { |
74 | t := Translation{ | 74 | t := Translation{ |
75 | Language: code, | 75 | Language: code, |
76 | FieldsLabels: labels, | 76 | FieldsLabels: labels, |
77 | } | 77 | } |
78 | p.Lang = append(p.Lang, t) | 78 | p.Lang = append(p.Lang, t) |
79 | } | 79 | } |
80 | 80 | ||
81 | // SetData ... | 81 | // SetData ... |
82 | func (p *Payload) SetData(data interface{}) { | 82 | func (p *Payload) SetData(data interface{}) { |
83 | p.Data = data | 83 | p.Data = data |
84 | } | 84 | } |
85 | 85 | ||
86 | // SetPaginationInfo ... | 86 | // SetPaginationInfo ... |
87 | func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParams) { | 87 | func (p *Payload) SetPaginationInfo(count, total int64, params PaginationParams) { |
88 | p.Links.Count = count | 88 | p.Links.Count = count |
89 | p.Links.Total = total | 89 | p.Links.Total = total |
90 | p.Links = params.links() | 90 | p.Links = params.links() |
91 | } | 91 | } |
92 | 92 | ||
93 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. | 93 | // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. |
94 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { | 94 | func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { |
95 | var err error | 95 | var err error |
96 | if drv != "ora" && drv != "mysql" { | 96 | if drv != "ora" && drv != "mysql" { |
97 | err = errors.New("driver not supported") | 97 | err = errors.New("driver not supported") |
98 | return err | 98 | return err |
99 | } | 99 | } |
100 | 100 | ||
101 | metaDriver = drv | 101 | metaDriver = drv |
102 | metadataDB = db | 102 | metadataDB = db |
103 | activeProject = project | 103 | activeProject = project |
104 | 104 | ||
105 | mu.Lock() | 105 | mu.Lock() |
106 | defer mu.Unlock() | 106 | defer mu.Unlock() |
107 | err = initMetadata(project) | 107 | err = initMetadata(project) |
108 | if err != nil { | 108 | if err != nil { |
109 | return err | 109 | return err |
110 | } | 110 | } |
111 | inited = true | 111 | inited = true |
112 | 112 | ||
113 | return nil | 113 | return nil |
114 | } | 114 | } |
115 | 115 | ||
116 | // EnableHotloading ... | 116 | // EnableHotloading ... |
117 | func EnableHotloading(interval int) { | 117 | func EnableHotloading(interval int) { |
118 | if interval > 0 { | 118 | if interval > 0 { |
119 | go hotload(interval) | 119 | go hotload(interval) |
120 | } | 120 | } |
121 | } | 121 | } |
122 | 122 | ||
123 | // GetMetadataForAllEntities ... | 123 | // GetMetadataForAllEntities ... |
124 | func GetMetadataForAllEntities() map[string]Payload { | 124 | func GetMetadataForAllEntities() map[string]Payload { |
125 | return metadata | 125 | return metadata |
126 | } | 126 | } |
127 | 127 | ||
128 | // GetMetadataForEntity ... | 128 | // GetMetadataForEntity ... |
129 | func GetMetadataForEntity(t string) (Payload, bool) { | 129 | func GetMetadataForEntity(t string) (Payload, bool) { |
130 | p, ok := metadata[t] | 130 | p, ok := metadata[t] |
131 | return p, ok | 131 | return p, ok |
132 | } | 132 | } |
133 | 133 | ||
134 | // QueEntityModelUpdate ... | 134 | // QueEntityModelUpdate ... |
135 | func QueEntityModelUpdate(entityType string, v interface{}) { | 135 | func QueEntityModelUpdate(entityType string, v interface{}) { |
136 | updateQue[entityType], _ = json.Marshal(v) | 136 | updateQue[entityType], _ = json.Marshal(v) |
137 | } | 137 | } |
138 | 138 | ||
139 | // UpdateEntityModels ... | 139 | // UpdateEntityModels ... |
140 | func UpdateEntityModels(command string) (total, upd, add int, err error) { | 140 | func UpdateEntityModels(command string) (total, upd, add int, err error) { |
141 | if command != "force" && command != "missing" { | 141 | if command != "force" && command != "missing" { |
142 | return total, 0, 0, errors.New("webutility: unknown command: " + command) | 142 | return total, 0, 0, errors.New("webutility: unknown command: " + command) |
143 | } | 143 | } |
144 | 144 | ||
145 | if !inited { | 145 | if !inited { |
146 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried") | 146 | return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried") |
147 | } | 147 | } |
148 | 148 | ||
149 | total = len(updateQue) | 149 | total = len(updateQue) |
150 | 150 | ||
151 | toUpdate := make([]string, 0) | 151 | toUpdate := make([]string, 0) |
152 | toAdd := make([]string, 0) | 152 | toAdd := make([]string, 0) |
153 | 153 | ||
154 | for k := range updateQue { | 154 | for k := range updateQue { |
155 | if _, exists := metadata[k]; exists { | 155 | if _, exists := metadata[k]; exists { |
156 | if command == "force" { | 156 | if command == "force" { |
157 | toUpdate = append(toUpdate, k) | 157 | toUpdate = append(toUpdate, k) |
158 | } | 158 | } |
159 | } else { | 159 | } else { |
160 | toAdd = append(toAdd, k) | 160 | toAdd = append(toAdd, k) |
161 | } | 161 | } |
162 | } | 162 | } |
163 | 163 | ||
164 | var uStmt *sql.Stmt | 164 | var uStmt *sql.Stmt |
165 | if metaDriver == "ora" { | 165 | if metaDriver == "ora" { |
166 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") | 166 | uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") |
167 | if err != nil { | 167 | if err != nil { |
168 | return | 168 | return |
169 | } | 169 | } |
170 | } else if metaDriver == "mysql" { | 170 | } else if metaDriver == "mysql" { |
171 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") | 171 | uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") |
172 | if err != nil { | 172 | if err != nil { |
173 | return | 173 | return |
174 | } | 174 | } |
175 | } | 175 | } |
176 | for _, k := range toUpdate { | 176 | for _, k := range toUpdate { |
177 | _, err = uStmt.Exec(string(updateQue[k]), k) | 177 | _, err = uStmt.Exec(string(updateQue[k]), k) |
178 | if err != nil { | 178 | if err != nil { |
179 | return | 179 | return |
180 | } | 180 | } |
181 | upd++ | 181 | upd++ |
182 | } | 182 | } |
183 | 183 | ||
184 | blankPayload, _ := json.Marshal(Payload{}) | 184 | blankPayload, _ := json.Marshal(Payload{}) |
185 | var iStmt *sql.Stmt | 185 | var iStmt *sql.Stmt |
186 | if metaDriver == "ora" { | 186 | if metaDriver == "ora" { |
187 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") | 187 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") |
188 | if err != nil { | 188 | if err != nil { |
189 | return | 189 | return |
190 | } | 190 | } |
191 | } else if metaDriver == "mysql" { | 191 | } else if metaDriver == "mysql" { |
192 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") | 192 | iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") |
193 | if err != nil { | 193 | if err != nil { |
194 | return | 194 | return |
195 | } | 195 | } |
196 | } | 196 | } |
197 | for _, k := range toAdd { | 197 | for _, k := range toAdd { |
198 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) | 198 | _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) |
199 | if err != nil { | 199 | if err != nil { |
200 | return | 200 | return |
201 | } | 201 | } |
202 | metadata[k] = Payload{} | 202 | metadata[k] = Payload{} |
203 | add++ | 203 | add++ |
204 | } | 204 | } |
205 | 205 | ||
206 | return total, upd, add, nil | 206 | return total, upd, add, nil |
207 | } | 207 | } |
208 | 208 | ||
209 | func initMetadata(project string) error { | 209 | func initMetadata(project string) error { |
210 | rows, err := metadataDB.Query(`select | 210 | rows, err := metadataDB.Query(`select |
211 | entity_type, | 211 | entity_type, |
212 | metadata | 212 | metadata |
213 | from entities | 213 | from entities |
214 | where projekat = ` + fmt.Sprintf("'%s'", project)) | 214 | where projekat = ` + fmt.Sprintf("'%s'", project)) |
215 | if err != nil { | 215 | if err != nil { |
216 | return err | 216 | return err |
217 | } | 217 | } |
218 | defer rows.Close() | 218 | defer rows.Close() |
219 | 219 | ||
220 | if len(metadata) > 0 { | 220 | if len(metadata) > 0 { |
221 | metadata = nil | 221 | metadata = nil |
222 | } | 222 | } |
223 | metadata = make(map[string]Payload) | 223 | metadata = make(map[string]Payload) |
224 | for rows.Next() { | 224 | for rows.Next() { |
225 | var name, load string | 225 | var name, load string |
226 | rows.Scan(&name, &load) | 226 | rows.Scan(&name, &load) |
227 | 227 | ||
228 | p := Payload{} | 228 | p := Payload{} |
229 | err := json.Unmarshal([]byte(load), &p) | 229 | err := json.Unmarshal([]byte(load), &p) |
230 | if err != nil { | 230 | if err != nil { |
231 | fmt.Printf("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) | 231 | fmt.Printf("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) |
232 | } else { | 232 | } else { |
233 | metadata[name] = p | 233 | metadata[name] = p |
234 | } | 234 | } |
235 | } | 235 | } |
236 | 236 | ||
237 | return nil | 237 | return nil |
238 | } | 238 | } |
239 | 239 | ||
240 | // LoadMetadataFromFile expects file in format: | 240 | // LoadMetadataFromFile expects file in format: |
241 | // | 241 | // |
242 | // [ payload A identifier ] | 242 | // [ payload A identifier ] |
243 | // key1 = value1 | 243 | // key1 = value1 |
244 | // key2 = value2 | 244 | // key2 = value2 |
245 | // ... | 245 | // ... |
246 | // [ payload B identifier ] | 246 | // [ payload B identifier ] |
247 | // key1 = value1 | 247 | // key1 = value1 |
248 | // key2 = value2 | 248 | // key2 = value2 |
249 | // ... | 249 | // ... |
250 | // | 250 | // |
251 | // TODO(marko): Currently supports only one hardcoded language... | 251 | // TODO(marko): Currently supports only one hardcoded language... |
252 | func LoadMetadataFromFile(path string) error { | 252 | func LoadMetadataFromFile(path string) error { |
253 | lines, err := ReadFileLines(path) | 253 | lines, err := ReadFileLines(path) |
254 | if err != nil { | 254 | if err != nil { |
255 | return err | 255 | return err |
256 | } | 256 | } |
257 | 257 | ||
258 | metadata = make(map[string]Payload) | 258 | metadata = make(map[string]Payload) |
259 | 259 | ||
260 | var name string | 260 | var name string |
261 | for i, l := range lines { | 261 | for i, l := range lines { |
262 | // skip empty lines | 262 | // skip empty lines |
263 | if l = strings.TrimSpace(l); len(l) == 0 { | 263 | if l = strings.TrimSpace(l); len(l) == 0 { |
264 | continue | 264 | continue |
265 | } | 265 | } |
266 | 266 | ||
267 | if IsWrappedWith(l, "[", "]") { | 267 | if IsWrappedWith(l, "[", "]") { |
268 | name = strings.Trim(l, "[]") | 268 | name = strings.Trim(l, "[]") |
269 | p := Payload{} | 269 | p := Payload{} |
270 | p.addLang("sr", make(map[string]string)) | 270 | p.addLang("sr", make(map[string]string)) |
271 | metadata[name] = p | 271 | metadata[name] = p |
272 | continue | 272 | continue |
273 | } | 273 | } |
274 | 274 | ||
275 | if name == "" { | 275 | if name == "" { |
276 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]", i+1, l) | 276 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [no header] [%s]", i+1, l) |
277 | } | 277 | } |
278 | 278 | ||
279 | parts := strings.Split(l, "=") | 279 | parts := strings.Split(l, "=") |
280 | if len(parts) != 2 { | 280 | if len(parts) != 2 { |
281 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]", i+1, l) | 281 | return fmt.Errorf("webutility: LoadMetadataFromFile: error on line %d: [invalid format] [%s]", i+1, l) |
282 | } | 282 | } |
283 | 283 | ||
284 | k := strings.TrimSpace(parts[0]) | 284 | k := strings.TrimSpace(parts[0]) |
285 | v := strings.TrimSpace(parts[1]) | 285 | v := strings.TrimSpace(parts[1]) |
286 | if v != "-" { | 286 | if v != "-" { |
287 | metadata[name].Lang[0].FieldsLabels[k] = v | 287 | metadata[name].Lang[0].FieldsLabels[k] = v |
288 | } | 288 | } |
289 | } | 289 | } |
290 | 290 | ||
291 | return nil | 291 | return nil |
292 | } | 292 | } |
293 | 293 | ||
294 | func hotload(n int) { | 294 | func hotload(n int) { |
295 | entityScan := make(map[string]int64) | 295 | entityScan := make(map[string]int64) |
296 | firstCheck := true | 296 | firstCheck := true |
297 | for { | 297 | for { |
298 | time.Sleep(time.Duration(n) * time.Second) | 298 | time.Sleep(time.Duration(n) * time.Second) |
299 | rows, err := metadataDB.Query(`select | 299 | rows, err := metadataDB.Query(`select |
300 | ora_rowscn, | 300 | ora_rowscn, |
301 | entity_type | 301 | entity_type |
302 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) | 302 | from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) |
303 | if err != nil { | 303 | if err != nil { |
304 | fmt.Printf("webutility: hotload failed: %v\n", err) | 304 | fmt.Printf("webutility: hotload failed: %v\n", err) |
305 | time.Sleep(time.Duration(n) * time.Second) | 305 | time.Sleep(time.Duration(n) * time.Second) |
306 | continue | 306 | continue |
307 | } | 307 | } |
308 | 308 | ||
309 | var toRefresh []string | 309 | var toRefresh []string |
310 | for rows.Next() { | 310 | for rows.Next() { |
311 | var scanID int64 | 311 | var scanID int64 |
312 | var entity string | 312 | var entity string |
313 | rows.Scan(&scanID, &entity) | 313 | rows.Scan(&scanID, &entity) |
314 | oldID, ok := entityScan[entity] | 314 | oldID, ok := entityScan[entity] |
315 | if !ok || oldID != scanID { | 315 | if !ok || oldID != scanID { |
316 | entityScan[entity] = scanID | 316 | entityScan[entity] = scanID |
317 | toRefresh = append(toRefresh, entity) | 317 | toRefresh = append(toRefresh, entity) |
318 | } | 318 | } |
319 | } | 319 | } |
320 | rows.Close() | 320 | rows.Close() |
321 | 321 | ||
322 | if rows.Err() != nil { | 322 | if rows.Err() != nil { |
323 | fmt.Printf("webutility: hotload rset error: %v\n", rows.Err()) | 323 | fmt.Printf("webutility: hotload rset error: %v\n", rows.Err()) |
324 | time.Sleep(time.Duration(n) * time.Second) | 324 | time.Sleep(time.Duration(n) * time.Second) |
325 | continue | 325 | continue |
326 | } | 326 | } |
327 | 327 | ||
328 | if len(toRefresh) > 0 && !firstCheck { | 328 | if len(toRefresh) > 0 && !firstCheck { |
329 | mu.Lock() | 329 | mu.Lock() |
330 | refreshMetadata(toRefresh) | 330 | refreshMetadata(toRefresh) |
331 | mu.Unlock() | 331 | mu.Unlock() |
332 | } | 332 | } |
333 | if firstCheck { | 333 | if firstCheck { |
334 | firstCheck = false | 334 | firstCheck = false |
335 | } | 335 | } |
336 | } | 336 | } |
337 | } | 337 | } |
338 | 338 | ||
339 | func refreshMetadata(entities []string) { | 339 | func refreshMetadata(entities []string) { |
340 | for _, e := range entities { | 340 | for _, e := range entities { |
341 | fmt.Printf("refreshing %s\n", e) | 341 | fmt.Printf("refreshing %s\n", e) |
342 | rows, err := metadataDB.Query(`select | 342 | rows, err := metadataDB.Query(`select |
343 | metadata | 343 | metadata |
344 | from entities | 344 | from entities |
345 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + | 345 | where projekat = ` + fmt.Sprintf("'%s'", activeProject) + |
346 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) | 346 | ` and entity_type = ` + fmt.Sprintf("'%s'", e)) |
347 | 347 | ||
348 | if err != nil { | 348 | if err != nil { |
349 | fmt.Printf("webutility: refresh: prep: %v\n", err) | 349 | fmt.Printf("webutility: refresh: prep: %v\n", err) |
350 | rows.Close() | 350 | rows.Close() |
351 | continue | 351 | continue |
352 | } | 352 | } |
353 | 353 | ||
354 | for rows.Next() { | 354 | for rows.Next() { |
355 | var load string | 355 | var load string |
356 | rows.Scan(&load) | 356 | rows.Scan(&load) |
357 | p := Payload{} | 357 | p := Payload{} |
358 | err := json.Unmarshal([]byte(load), &p) | 358 | err := json.Unmarshal([]byte(load), &p) |
359 | if err != nil { | 359 | if err != nil { |
360 | fmt.Printf("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) | 360 | fmt.Printf("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) |
361 | } else { | 361 | } else { |
362 | metadata[e] = p | 362 | metadata[e] = p |
363 | } | 363 | } |
364 | } | 364 | } |
365 | rows.Close() | 365 | rows.Close() |
366 | } | 366 | } |
367 | } | 367 | } |
368 | 368 | ||
369 | /* | 369 | /* |
370 | func ModifyMetadataForEntity(entityType string, p *Payload) error { | 370 | func ModifyMetadataForEntity(entityType string, p *Payload) error { |
371 | md, err := json.Marshal(*p) | 371 | md, err := json.Marshal(*p) |
372 | if err != nil { | 372 | if err != nil { |
373 | return err | 373 | return err |
374 | } | 374 | } |
375 | 375 | ||
376 | mu.Lock() | 376 | mu.Lock() |
377 | defer mu.Unlock() | 377 | defer mu.Unlock() |
378 | _, err = metadataDB.PrepAndExe(`update entities set | 378 | _, err = metadataDB.PrepAndExe(`update entities set |
379 | metadata = :1 | 379 | metadata = :1 |
380 | where projekat = :2 | 380 | where projekat = :2 |
381 | and entity_type = :3`, | 381 | and entity_type = :3`, |
382 | string(md), | 382 | string(md), |
383 | activeProject, | 383 | activeProject, |
384 | entityType) | 384 | entityType) |
385 | if err != nil { | 385 | if err != nil { |
386 | return err | 386 | return err |
387 | } | 387 | } |
388 | return nil | 388 | return nil |
389 | } | 389 | } |
390 | 390 | ||
391 | func DeleteEntityModel(entityType string) error { | 391 | func DeleteEntityModel(entityType string) error { |
392 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) | 392 | _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) |
393 | if err == nil { | 393 | if err == nil { |
394 | mu.Lock() | 394 | mu.Lock() |
395 | delete(metadata, entityType) | 395 | delete(metadata, entityType) |
396 | mu.Unlock() | 396 | mu.Unlock() |
397 | } | 397 | } |
398 | return err | 398 | return err |
399 | } | 399 | } |
400 | */ | 400 | */ |
401 | 401 |
quicksort.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | type QSortDirection int | 3 | type QSortDirection int |
4 | 4 | ||
5 | const ( | 5 | const ( |
6 | QSortAscending = 0 | 6 | QSortAscending QSortDirection = iota |
7 | QSortDescending = 1 | 7 | QSortDescending |
8 | ) | 8 | ) |
9 | 9 | ||
10 | // QuickSortable is an interface for quicksorting slices. | 10 | // QuickSortable is an interface for quicksorting slices. |
11 | type QuickSortable interface { | 11 | type QuickSortable interface { |
12 | Swap(i, j int) | 12 | Swap(i, j int) |
13 | Compare(i, j int) int | 13 | Compare(i, j int) int |
14 | Len() int | 14 | Len() int |
15 | } | 15 | } |
16 | 16 | ||
17 | // Quicksort quicksorts que. | 17 | // Quicksort quicksorts que. |
18 | func Quicksort(que QuickSortable, low, high int, dir QSortDirection) { | 18 | func Quicksort(que QuickSortable, low, high int, dir QSortDirection) { |
19 | if low >= high { | 19 | if low >= high { |
20 | return | 20 | return |
21 | } | 21 | } |
22 | 22 | ||
23 | if dir != QSortAscending && dir != QSortDescending { | 23 | if dir != QSortAscending && dir != QSortDescending { |
24 | return | 24 | return |
25 | } | 25 | } |
26 | 26 | ||
27 | index := partition(que, low, high, dir) | 27 | index := partition(que, low, high, dir) |
28 | Quicksort(que, low, index-1, dir) | 28 | Quicksort(que, low, index-1, dir) |
29 | Quicksort(que, index+1, high, dir) | 29 | Quicksort(que, index+1, high, dir) |
30 | } | 30 | } |
31 | 31 | ||
32 | func partition(que QuickSortable, low, high int, dir QSortDirection) int { | 32 | func partition(que QuickSortable, low, high int, dir QSortDirection) int { |
33 | swap := 0 | ||
34 | // -1 -> i > j | ||
35 | // 1 -> i < j | ||
36 | if dir == QSortDescending { | ||
37 | swap = -1 | ||
38 | } else { | ||
39 | swap = 1 | ||
40 | } | ||
41 | |||
33 | i := low - 1 | 42 | i := low - 1 |
34 | for j := low; j <= high-1; j++ { | 43 | for j := low; j <= high-1; j++ { |
35 | if dir == QSortAscending { | 44 | if que.Compare(j, high) == swap { |
36 | if que.Compare(j, high) == -1 { | 45 | i++ |
37 | i++ | 46 | que.Swap(i, j) |
38 | que.Swap(i, j) | ||
39 | } | ||
40 | } else if dir == QSortDescending { | ||
41 | if que.Compare(j, high) == 1 { | ||
42 | i++ | ||
43 | que.Swap(i, j) | ||
44 | } |
string_util.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | 4 | "fmt" |
5 | "strconv" | 5 | "strconv" |
6 | "strings" | 6 | "strings" |
7 | "unicode" | 7 | "unicode" |
8 | ) | 8 | ) |
9 | 9 | ||
10 | const sanitisationPatern = "\"';&*<>=\\`:" | 10 | const sanitisationPatern = "\"';&*<>=\\`:" |
11 | 11 | ||
12 | // SanitiseString removes characters from s found in patern and returns new modified string. | 12 | // SanitiseString removes characters from s found in patern and returns new modified string. |
13 | func SanitiseString(s string) string { | 13 | func SanitiseString(s string) string { |
14 | return ReplaceAny(s, sanitisationPatern, "") | 14 | return ReplaceAny(s, sanitisationPatern, "") |
15 | } | 15 | } |
16 | 16 | ||
17 | // IsWrappedWith ... | 17 | // IsWrappedWith ... |
18 | func IsWrappedWith(src, begin, end string) bool { | 18 | func IsWrappedWith(src, begin, end string) bool { |
19 | return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end) | 19 | return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end) |
20 | } | 20 | } |
21 | 21 | ||
22 | // ParseInt64Arr ... | 22 | // ParseInt64Arr ... |
23 | func ParseInt64Arr(s, sep string) (arr []int64) { | 23 | func ParseInt64Arr(s, sep string) (arr []int64) { |
24 | s = strings.TrimSpace(s) | 24 | s = strings.TrimSpace(s) |
25 | if s != "" { | 25 | if s == "" { |
26 | parts := strings.Split(s, sep) | 26 | return |
27 | arr = make([]int64, len(parts)) | 27 | } |
28 | for i, p := range parts { | 28 | parts := strings.Split(s, sep) |
29 | num := StringToInt64(p) | 29 | arr = make([]int64, len(parts)) |
30 | arr[i] = num | 30 | for i, p := range parts { |
31 | } | 31 | num := StringToInt64(p) |
32 | arr[i] = num | ||
32 | } | 33 | } |
33 | 34 | ||
34 | return arr | 35 | return arr |
35 | } | 36 | } |
36 | 37 | ||
37 | // Int64SliceToString ... | 38 | // Int64SliceToString ... |
38 | func Int64SliceToString(arr []int64) (s string) { | 39 | func Int64SliceToString(arr []int64) (s string) { |
39 | if len(arr) == 0 { | 40 | if len(arr) == 0 { |
40 | return "" | 41 | return "" |
41 | } | 42 | } |
42 | 43 | ||
43 | s += fmt.Sprintf("%d", arr[0]) | 44 | s += fmt.Sprintf("%d", arr[0]) |
44 | for i := 1; i < len(arr); i++ { | 45 | for i := 1; i < len(arr); i++ { |
45 | s += fmt.Sprintf(",%d", arr[i]) | 46 | s += fmt.Sprintf(",%d", arr[i]) |
46 | } | 47 | } |
47 | 48 | ||
48 | return s | 49 | return s |
49 | } | 50 | } |
50 | 51 | ||
51 | // CombineStrings ... | 52 | // CombineStrings ... |
52 | func CombineStrings(s1, s2, s3 string) string { | 53 | func CombineStrings(s1, s2, s3 string) string { |
53 | s1 = strings.TrimSpace(s1) | 54 | s1 = strings.TrimSpace(s1) |
54 | s2 = strings.TrimSpace(s2) | 55 | s2 = strings.TrimSpace(s2) |
55 | 56 | ||
56 | if s1 != "" && s2 != "" { | 57 | if s1 != "" && s2 != "" { |
57 | s1 += s3 + s2 | 58 | s1 += s3 + s2 |
58 | } else { | 59 | } else { |
59 | s1 += s2 | 60 | s1 += s2 |
60 | } | 61 | } |
61 | 62 | ||
62 | return s1 | 63 | return s1 |
63 | } | 64 | } |
64 | 65 | ||
65 | // ReplaceAny replaces any of the characters from patern found in s with r and returns a new resulting string. | 66 | // ReplaceAny replaces any of the characters from patern found in s with r and returns a new resulting string. |
66 | func ReplaceAny(s, patern, r string) (n string) { | 67 | func ReplaceAny(s, patern, r string) (n string) { |
67 | n = s | 68 | n = s |
68 | for _, c := range patern { | 69 | for _, c := range patern { |
69 | n = strings.Replace(n, string(c), r, -1) | 70 | n = strings.Replace(n, string(c), r, -1) |
70 | } | 71 | } |
71 | return n | 72 | return n |
72 | } | 73 | } |
73 | 74 | ||
74 | // StringToBool ... | 75 | // StringToBool ... |
75 | func StringToBool(s string) bool { | 76 | func StringToBool(s string) bool { |
76 | res, _ := strconv.ParseBool(s) | 77 | res, _ := strconv.ParseBool(s) |
77 | return res | 78 | return res |
78 | } | 79 | } |
79 | 80 | ||
80 | // BoolToString ... | 81 | // BoolToString ... |
81 | func BoolToString(b bool) string { | 82 | func BoolToString(b bool) string { |
82 | return fmt.Sprintf("%b", b) | 83 | return fmt.Sprintf("%b", b) |
83 | } | 84 | } |
84 | 85 | ||
85 | // StringSliceContains ... | 86 | // StringSliceContains ... |
86 | func StringSliceContains(slice []string, s string) bool { | 87 | func StringSliceContains(slice []string, s string) bool { |
87 | for i := range slice { | 88 | for i := range slice { |
88 | if slice[i] == s { | 89 | if slice[i] == s { |
89 | return true | 90 | return true |
90 | } | 91 | } |
91 | } | 92 | } |
92 | return false | 93 | return false |
93 | } | 94 | } |
94 | 95 | ||
95 | func SplitString(s, sep string) (res []string) { | 96 | func SplitString(s, sep string) (res []string) { |
96 | parts := strings.Split(s, sep) | 97 | parts := strings.Split(s, sep) |
97 | for _, p := range parts { | 98 | for _, p := range parts { |
98 | if p != "" { | 99 | if p != "" { |
99 | res = append(res, p) | 100 | res = append(res, p) |
100 | } | 101 | } |
101 | } | 102 | } |
102 | return res | 103 | return res |
103 | } | 104 | } |
104 | 105 | ||
105 | // StringAt ... | 106 | // StringAt ... |
106 | func StringAt(s string, index int) string { | 107 | func StringAt(s string, index int) string { |
107 | if len(s)-1 < index || index < 0 { | 108 | if len(s)-1 < index || index < 0 { |
108 | return "" | 109 | return "" |
109 | } | 110 | } |
110 | 111 | ||
111 | return string(s[index]) | 112 | return string(s[index]) |
112 | } | 113 | } |
113 | 114 | ||
114 | // SplitText ... | 115 | // SplitText ... |
115 | func SplitText(s string, maxLen int) (lines []string) { | 116 | func SplitText(s string, maxLen int) (lines []string) { |
116 | runes := []rune(s) | 117 | runes := []rune(s) |
117 | 118 | ||
118 | i, start, sep, l := 0, 0, 0, 0 | 119 | i, start, sep, l := 0, 0, 0, 0 |
119 | for i = 0; i < len(runes); i++ { | 120 | for i = 0; i < len(runes); i++ { |
120 | c := runes[i] | 121 | c := runes[i] |
121 | 122 | ||
122 | if unicode.IsSpace(c) { | 123 | if unicode.IsSpace(c) { |
123 | sep = i | 124 | sep = i |
124 | } | 125 | } |
125 | 126 | ||
126 | if c == '\n' { | 127 | if c == '\n' { |
127 | if start != sep { | 128 | if start != sep { |
128 | lines = append(lines, string(runes[start:sep])) | 129 | lines = append(lines, string(runes[start:sep])) |
129 | } | 130 | } |
130 | start = i | 131 | start = i |
131 | sep = i | 132 | sep = i |
132 | l = 0 | 133 | l = 0 |
133 | } else if l >= maxLen { | 134 | } else if l >= maxLen { |
134 | if start != sep { | 135 | if start != sep { |
135 | lines = append(lines, string(runes[start:sep])) | 136 | lines = append(lines, string(runes[start:sep])) |
136 | sep = i | 137 | sep = i |
137 | start = i - 1 | 138 | start = i - 1 |
138 | l = 0 | 139 | l = 0 |
139 | } | 140 | } |
140 | } else { | 141 | } else { |
141 | l++ | 142 | l++ |
142 | } | 143 | } |
143 | } | 144 | } |
144 | if start != i-1 { | 145 | if start != i-1 { |
145 | lines = append(lines, string(runes[start:i-1])) | 146 | lines = append(lines, string(runes[start:i-1])) |
146 | } | 147 | } |
147 | 148 | ||
148 | return lines | 149 | return lines |
149 | } | 150 | } |
150 | 151 | ||
151 | func CutTextWithThreeDots(txt string, maxLen int) string { | 152 | func CutTextWithThreeDots(txt string, maxLen int) string { |
152 | if len(txt) < maxLen || len(txt) <= 3 { | 153 | if len(txt) < maxLen || len(txt) <= 3 { |
153 | return txt | 154 | return txt |
154 | } | 155 | } |
155 | 156 | ||
156 | return txt[:maxLen-3] + "..." | 157 | return txt[:maxLen-3] + "..." |
157 | } | 158 | } |
158 | 159 | ||
159 | const threeDots = "\u2056\u2056\u2056" | 160 | const threeDots = "\u2056\u2056\u2056" |
160 | 161 | ||
161 | func LimitTextWithThreeDots(txt string, maxLen int) string { | 162 | func LimitTextWithThreeDots(txt string, maxLen int) string { |
162 | if len(txt) <= maxLen { | 163 | if len(txt) <= maxLen { |
163 | return txt | 164 | return txt |
164 | } | 165 | } |
165 | 166 | ||
166 | return txt[:maxLen] + threeDots | 167 | return txt[:maxLen] + threeDots |
167 | } | 168 | } |
168 | 169 | ||
169 | func LimitMSWordTextWithThreeDots(txt string, maxLen int) string { | 170 | func LimitMSWordTextWithThreeDots(txt string, maxLen int) string { |
170 | if len(txt) <= maxLen { | 171 | if len(txt) <= maxLen { |
171 | return txt | 172 | return txt |
172 | } | 173 | } |
173 | 174 | ||
174 | return txt[:maxLen] + "..." | 175 | return txt[:maxLen] + "..." |
175 | } | 176 | } |
176 | 177 | ||
178 | func ThreeDots(txt string, maxLen int) string { | ||
179 | if len(txt) <= maxLen { | ||
180 | return txt | ||
181 | } | ||
182 | |||
183 | return txt[:maxLen] + "..." | ||
184 | } | ||
185 | |||
177 | // SplitStringAtWholeWords ... | 186 | // SplitStringAtWholeWords ... |
178 | func SplitStringAtWholeWords(s string, maxLen int) (res []string) { | 187 | func SplitStringAtWholeWords(s string, maxLen int) (res []string) { |
179 | parts := strings.Split(s, " ") | 188 | parts := strings.Split(s, " ") |
180 | 189 | ||
181 | res = append(res, parts[0]) | 190 | res = append(res, parts[0]) |
182 | i := 0 | 191 | i := 0 |
183 | for j := 1; j < len(parts); j++ { | 192 | for j := 1; j < len(parts); j++ { |
184 | p := strings.TrimSpace(parts[j]) | 193 | p := strings.TrimSpace(parts[j]) |
185 | if len(p) > maxLen { | 194 | if len(p) > maxLen { |
186 | // TODO(marko): check if maxLen is >= 3 | 195 | // TODO(marko): check if maxLen is >= 3 |
187 | p = p[0 : maxLen-3] | 196 | p = p[0 : maxLen-3] |
188 | p += "..." | 197 | p += "..." |
189 | } | 198 | } |
190 | if len(res[i])+len(p)+1 <= maxLen { | 199 | if len(res[i])+len(p)+1 <= maxLen { |
191 | res[i] += " " + p | 200 | res[i] += " " + p |
192 | } else { | 201 | } else { |
193 | res = append(res, p) | 202 | res = append(res, p) |
194 | i++ | 203 | i++ |
195 | } | 204 | } |
196 | } | 205 | } |
197 | 206 | ||
198 | return res | 207 | return res |
199 | } | 208 | } |
209 | |||
210 | // StringToInt64 ... | ||
211 | func StringToInt64(s string) int64 { | ||
212 | i, _ := strconv.ParseInt(s, 10, 64) | ||
213 | return i | ||
214 | } | ||
215 | |||
216 | // StringToFloat64 ... | ||
217 | func StringToFloat64(s string) float64 { | ||
218 | f, _ := strconv.ParseFloat(s, 64) | ||
219 | return f | ||
220 | } | ||
221 | |||
222 | func StringToValidInt64(s string) (int64, bool) { | ||
223 | i, err := strconv.ParseInt(s, 10, 64) | ||
224 | if err != nil { | ||
225 | return i, false | ||
226 | } | ||
227 | return i, true | ||
228 | } | ||
200 | 229 |