Commit 1df829121974bb54eb7ab678d9317eee8160736a
Exists in
master
Merge branch 'master' of ssh://git.to-net.rs:6322/marko.tikvic/webutility
Showing
6 changed files
Show diff stats
guid.go
File was created | 1 | package webutility | |
2 | |||
3 | import ( | ||
4 | "crypto/rand" | ||
5 | "fmt" | ||
6 | ) | ||
7 | |||
8 | // GUID ... | ||
9 | func GUID() (string, error) { | ||
10 | b := make([]byte, 16) | ||
11 | _, err := rand.Read(b) | ||
12 | if err != nil { | ||
13 | return "", err | ||
14 | } | ||
15 | id := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) | ||
16 | return id, nil | ||
17 | } | ||
18 |
middleware/main.go
1 | package middleware | 1 | package middleware |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | ||
5 | "io/ioutil" | ||
4 | "net/http" | 6 | "net/http" |
7 | |||
8 | web "git.to-net.rs/marko.tikvic/webutility" | ||
5 | ) | 9 | ) |
6 | 10 | ||
7 | func Headers(h http.HandlerFunc) http.HandlerFunc { | 11 | func Headers(h http.HandlerFunc) http.HandlerFunc { |
8 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h))) | 12 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h))) |
9 | } | 13 | } |
10 | 14 | ||
11 | func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc { | 15 | func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc { |
12 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h)))) | 16 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h)))) |
13 | } | 17 | } |
14 | 18 | ||
15 | func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc { | 19 | func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc { |
16 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h))))) | 20 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h))))) |
17 | } | 21 | } |
18 | 22 | ||
19 | func LogTraffic(h http.HandlerFunc) http.HandlerFunc { | 23 | func LogTraffic(h http.HandlerFunc) http.HandlerFunc { |
20 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h)))) | 24 | return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h)))) |
21 | } | 25 | } |
26 | |||
27 | func TrafficLogsHandler(w http.ResponseWriter, req *http.Request) { | ||
28 | switch req.Method { | ||
29 | case "GET": | ||
30 | files, err := ioutil.ReadDir(httpLogger.GetOutDir() + "/") | ||
31 | if err != nil { | ||
32 | web.InternalServerError(w, req, err.Error()) | ||
33 | return | ||
34 | } | ||
35 | |||
36 | web.SetContentType(w, "text/html; charset=utf-8") | ||
37 | web.SetResponseStatus(w, http.StatusOK) | ||
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 | |||
64 | case "POST": | ||
65 | web.SetContentType(w, "text/html; charset=utf-8") | ||
66 | |||
67 | logfile := req.FormValue("logfile") | ||
68 | content, err := web.ReadFileContent(httpLogger.GetOutDir() + "/" + logfile) | ||
69 | if err != nil { | ||
70 | web.InternalServerError(w, req, err.Error()) | ||
71 | return | ||
72 | } | ||
73 | web.SetResponseStatus(w, http.StatusOK) | ||
74 | web.WriteResponse(w, []byte("<body style='background-color: black; color: white'>")) | ||
75 | web.WriteResponse(w, []byte("<pre>")) | ||
76 | web.WriteResponse(w, content) | ||
77 | web.WriteResponse(w, []byte("</pre></body>")) | ||
78 | return | ||
79 | } | ||
80 | } | ||
22 | 81 |
middleware/middleware.go
1 | package middleware | 1 | package middleware |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "net/http" | 4 | "net/http" |
5 | "time" | 5 | "time" |
6 | 6 | ||
7 | "git.to-net.rs/marko.tikvic/gologger" | 7 | "git.to-net.rs/marko.tikvic/gologger" |
8 | 8 | ||
9 | web "git.to-net.rs/marko.tikvic/webutility" | 9 | web "git.to-net.rs/marko.tikvic/webutility" |
10 | ) | 10 | ) |
11 | 11 | ||
12 | var httpLogger *gologger.Logger | 12 | var httpLogger *gologger.Logger |
13 | 13 | ||
14 | func SetAccessControlHeaders(h http.HandlerFunc) http.HandlerFunc { | 14 | func SetAccessControlHeaders(h http.HandlerFunc) http.HandlerFunc { |
15 | return func(w http.ResponseWriter, req *http.Request) { | 15 | return func(w http.ResponseWriter, req *http.Request) { |
16 | web.SetAccessControlHeaders(w) | 16 | web.SetAccessControlHeaders(w) |
17 | 17 | ||
18 | h(w, req) | 18 | h(w, req) |
19 | } | 19 | } |
20 | } | 20 | } |
21 | 21 | ||
22 | // IgnoreOptionsRequests ... | 22 | // IgnoreOptionsRequests ... |
23 | func IgnoreOptionsRequests(h http.HandlerFunc) http.HandlerFunc { | 23 | func IgnoreOptionsRequests(h http.HandlerFunc) http.HandlerFunc { |
24 | return func(w http.ResponseWriter, req *http.Request) { | 24 | return func(w http.ResponseWriter, req *http.Request) { |
25 | if req.Method == http.MethodOptions { | 25 | if req.Method == http.MethodOptions { |
26 | return | 26 | return |
27 | } | 27 | } |
28 | 28 | ||
29 | h(w, req) | 29 | h(w, req) |
30 | } | 30 | } |
31 | } | 31 | } |
32 | 32 | ||
33 | // ParseForm ... | 33 | // ParseForm ... |
34 | func ParseForm(h http.HandlerFunc) http.HandlerFunc { | 34 | func ParseForm(h http.HandlerFunc) http.HandlerFunc { |
35 | return func(w http.ResponseWriter, req *http.Request) { | 35 | return func(w http.ResponseWriter, req *http.Request) { |
36 | err := req.ParseForm() | 36 | err := req.ParseForm() |
37 | if err != nil { | 37 | if err != nil { |
38 | web.BadRequest(w, req, err.Error()) | 38 | web.BadRequest(w, req, err.Error()) |
39 | return | 39 | return |
40 | } | 40 | } |
41 | 41 | ||
42 | h(w, req) | 42 | h(w, req) |
43 | } | 43 | } |
44 | } | 44 | } |
45 | 45 | ||
46 | // ParseMultipartForm ... | 46 | // ParseMultipartForm ... |
47 | func ParseMultipartForm(h http.HandlerFunc) http.HandlerFunc { | 47 | func ParseMultipartForm(h http.HandlerFunc) http.HandlerFunc { |
48 | return func(w http.ResponseWriter, req *http.Request) { | 48 | return func(w http.ResponseWriter, req *http.Request) { |
49 | err := req.ParseMultipartForm(32 << 20) | 49 | err := req.ParseMultipartForm(32 << 20) |
50 | if err != nil { | 50 | if err != nil { |
51 | web.BadRequest(w, req, err.Error()) | 51 | web.BadRequest(w, req, err.Error()) |
52 | return | 52 | return |
53 | } | 53 | } |
54 | 54 | ||
55 | h(w, req) | 55 | h(w, req) |
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | // SetLogger ... | 59 | // SetLogger ... |
60 | func SetLogger(logger *gologger.Logger) { | 60 | func SetLogger(logger *gologger.Logger) { |
61 | httpLogger = logger | 61 | httpLogger = logger |
62 | } | 62 | } |
63 | 63 | ||
64 | func StartLogging(filename, dir string) (err error) { | ||
65 | if httpLogger, err = gologger.New(filename, dir, gologger.MaxLogSize1MB); err != nil { | ||
66 | return err | ||
67 | } | ||
68 | return nil | ||
69 | } | ||
70 | |||
71 | func CloseLogger() { | ||
72 | httpLogger.Close() | ||
73 | } | ||
74 | |||
64 | // LogHTTP ... | 75 | // LogHTTP ... |
65 | func LogHTTP(h http.HandlerFunc) http.HandlerFunc { | 76 | func LogHTTP(h http.HandlerFunc) http.HandlerFunc { |
66 | return func(w http.ResponseWriter, req *http.Request) { | 77 | return func(w http.ResponseWriter, req *http.Request) { |
67 | if httpLogger == nil { | 78 | if httpLogger == nil { |
68 | h(w, req) | 79 | h(w, req) |
69 | return | 80 | return |
70 | } | 81 | } |
71 | 82 | ||
72 | t1 := time.Now() | 83 | t1 := time.Now() |
73 | 84 | ||
74 | claims, _ := web.GetTokenClaims(req) | 85 | claims, _ := web.GetTokenClaims(req) |
75 | in := httpLogger.LogHTTPRequest(req, claims.Username) | 86 | in := httpLogger.LogHTTPRequest(req, claims.Username) |
76 | 87 | ||
77 | rec := web.NewStatusRecorder(w) | 88 | rec := web.NewStatusRecorder(w) |
78 | 89 | ||
79 | h(rec, req) | 90 | h(rec, req) |
80 | 91 | ||
81 | t2 := time.Now() | 92 | t2 := time.Now() |
82 | out := httpLogger.LogHTTPResponse(rec.Status(), t2.Sub(t1), rec.Size()) | 93 | out := httpLogger.LogHTTPResponse(rec.Status(), t2.Sub(t1), rec.Size()) |
83 | 94 | ||
84 | httpLogger.CombineHTTPLogs(in, out) | 95 | httpLogger.CombineHTTPLogs(in, out) |
85 | } | 96 | } |
86 | } | 97 | } |
87 | 98 | ||
88 | // Auth ... | 99 | // Auth ... |
89 | func Auth(roles string, h http.HandlerFunc) http.HandlerFunc { | 100 | func Auth(roles string, h http.HandlerFunc) http.HandlerFunc { |
90 | return func(w http.ResponseWriter, req *http.Request) { | 101 | return func(w http.ResponseWriter, req *http.Request) { |
91 | if _, err := web.AuthCheck(req, roles); err != nil { | 102 | if _, err := web.AuthCheck(req, roles); err != nil { |
92 | web.Unauthorized(w, req, err.Error()) | 103 | web.Unauthorized(w, req, err.Error()) |
93 | return | 104 | return |
94 | } | 105 | } |
95 | 106 | ||
96 | h(w, req) | 107 | h(w, req) |
97 | } | 108 | } |
98 | } | 109 | } |
99 | 110 |
server.go
File was created | 1 | package webutility | |
2 | |||
3 | import ( | ||
4 | "database/sql" | ||
5 | "fmt" | ||
6 | "net/http" | ||
7 | |||
8 | "git.to-net.rs/marko.tikvic/gologger" | ||
9 | "github.com/gorilla/mux" | ||
10 | ) | ||
11 | |||
12 | type Server struct { | ||
13 | DB *sql.DB | ||
14 | Router *mux.Router | ||
15 | Logger *gologger.Logger | ||
16 | Port string | ||
17 | UTCOffset int64 | ||
18 | } | ||
19 | |||
20 | func NewServer(dsn, port, logDir string, utcOffset int64) (s *Server, err error) { | ||
21 | s = new(Server) | ||
22 | |||
23 | s.Port = port | ||
24 | |||
25 | if s.DB, err = sql.Open("odbc", fmt.Sprintf("DSN=%s;", dsn)); err != nil { | ||
26 | return nil, err | ||
27 | } | ||
28 | |||
29 | s.Router = mux.NewRouter() | ||
30 | |||
31 | if s.Logger, err = gologger.New("err", logDir, gologger.MaxLogSize1MB); err != nil { | ||
32 | return nil, fmt.Errorf("can't create logger: %s", err.Error()) | ||
33 | } | ||
34 | |||
35 | s.UTCOffset = utcOffset | ||
36 | |||
37 | return s, nil | ||
38 | } | ||
39 | |||
40 | func (s *Server) Run() { | ||
41 | s.Logger.Print("Server listening on %s", s.Port) | ||
42 | s.Logger.PrintAndTrace(http.ListenAndServe(s.Port, s.Router).Error()) | ||
43 | } | ||
44 | |||
45 | func (s *Server) Cleanup() { | ||
46 | if s.DB != nil { | ||
47 | s.DB.Close() | ||
48 | } | ||
49 | |||
50 | if s.Logger != nil { | ||
51 | s.Logger.Close() | ||
52 | } | ||
53 | } | ||
54 | |||
55 | func (s *Server) StartTransaction() (*sql.Tx, error) { | ||
56 | return s.DB.Begin() | ||
57 | } | ||
58 | |||
59 | func CommitChanges(tx *sql.Tx, err *error, opt ...error) { | ||
60 | if *err != nil { | ||
61 | tx.Rollback() | ||
62 | return | ||
63 | } | ||
64 | |||
65 | for _, e := range opt { | ||
66 | if e != nil { | ||
67 | tx.Rollback() | ||
68 | return | ||
69 | } | ||
70 | } | ||
71 | |||
72 | if *err = tx.Commit(); *err != nil { | ||
73 | tx.Rollback() | ||
74 | } | ||
75 | } | ||
76 |
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 | parts := strings.Split(s, sep) |
27 | arr = make([]int64, len(parts)) | 27 | arr = make([]int64, len(parts)) |
28 | for i, p := range parts { | 28 | for i, p := range parts { |
29 | num := StringToInt64(p) | 29 | num := StringToInt64(p) |
30 | arr[i] = num | 30 | arr[i] = num |
31 | } | 31 | } |
32 | } | 32 | } |
33 | 33 | ||
34 | return arr | 34 | return arr |
35 | } | 35 | } |
36 | 36 | ||
37 | // Int64SliceToString ... | 37 | // Int64SliceToString ... |
38 | func Int64SliceToString(arr []int64) (s string) { | 38 | func Int64SliceToString(arr []int64) (s string) { |
39 | if len(arr) == 0 { | 39 | if len(arr) == 0 { |
40 | return "" | 40 | return "" |
41 | } | 41 | } |
42 | 42 | ||
43 | s += fmt.Sprintf("%d", arr[0]) | 43 | s += fmt.Sprintf("%d", arr[0]) |
44 | for i := 1; i < len(arr); i++ { | 44 | for i := 1; i < len(arr); i++ { |
45 | s += fmt.Sprintf(",%d", arr[i]) | 45 | s += fmt.Sprintf(",%d", arr[i]) |
46 | } | 46 | } |
47 | 47 | ||
48 | return s | 48 | return s |
49 | } | 49 | } |
50 | 50 | ||
51 | // CombineStrings ... | 51 | // CombineStrings ... |
52 | func CombineStrings(s1, s2, s3 string) string { | 52 | func CombineStrings(s1, s2, s3 string) string { |
53 | s1 = strings.TrimSpace(s1) | 53 | s1 = strings.TrimSpace(s1) |
54 | s2 = strings.TrimSpace(s2) | 54 | s2 = strings.TrimSpace(s2) |
55 | 55 | ||
56 | if s1 != "" && s2 != "" { | 56 | if s1 != "" && s2 != "" { |
57 | s1 += s3 + s2 | 57 | s1 += s3 + s2 |
58 | } else { | 58 | } else { |
59 | s1 += s2 | 59 | s1 += s2 |
60 | } | 60 | } |
61 | 61 | ||
62 | return s1 | 62 | return s1 |
63 | } | 63 | } |
64 | 64 | ||
65 | // ReplaceAny replaces any of the characters from patern found in s with r and returns a new resulting string. | 65 | // 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) { | 66 | func ReplaceAny(s, patern, r string) (n string) { |
67 | n = s | 67 | n = s |
68 | for _, c := range patern { | 68 | for _, c := range patern { |
69 | n = strings.Replace(n, string(c), r, -1) | 69 | n = strings.Replace(n, string(c), r, -1) |
70 | } | 70 | } |
71 | return n | 71 | return n |
72 | } | 72 | } |
73 | 73 | ||
74 | // StringToBool ... | 74 | // StringToBool ... |
75 | func StringToBool(s string) bool { | 75 | func StringToBool(s string) bool { |
76 | res, _ := strconv.ParseBool(s) | 76 | res, _ := strconv.ParseBool(s) |
77 | return res | 77 | return res |
78 | } | 78 | } |
79 | 79 | ||
80 | // BoolToString ... | 80 | // BoolToString ... |
81 | func BoolToString(b bool) string { | 81 | func BoolToString(b bool) string { |
82 | return fmt.Sprintf("%b", b) | 82 | return fmt.Sprintf("%b", b) |
83 | } | 83 | } |
84 | 84 | ||
85 | // StringSliceContains ... | 85 | // StringSliceContains ... |
86 | func StringSliceContains(slice []string, s string) bool { | 86 | func StringSliceContains(slice []string, s string) bool { |
87 | for i := range slice { | 87 | for i := range slice { |
88 | if slice[i] == s { | 88 | if slice[i] == s { |
89 | return true | 89 | return true |
90 | } | 90 | } |
91 | } | 91 | } |
92 | return false | 92 | return false |
93 | } | 93 | } |
94 | 94 | ||
95 | func SplitString(s, sep string) (res []string) { | 95 | func SplitString(s, sep string) (res []string) { |
96 | parts := strings.Split(s, sep) | 96 | parts := strings.Split(s, sep) |
97 | for _, p := range parts { | 97 | for _, p := range parts { |
98 | if p != "" { | 98 | if p != "" { |
99 | res = append(res, p) | 99 | res = append(res, p) |
100 | } | 100 | } |
101 | } | 101 | } |
102 | return res | 102 | return res |
103 | } | 103 | } |
104 | 104 | ||
105 | // StringAt ... | 105 | // StringAt ... |
106 | func StringAt(s string, index int) string { | 106 | func StringAt(s string, index int) string { |
107 | if len(s)-1 < index || index < 0 { | 107 | if len(s)-1 < index || index < 0 { |
108 | return "" | 108 | return "" |
109 | } | 109 | } |
110 | 110 | ||
111 | return string(s[index]) | 111 | return string(s[index]) |
112 | } | 112 | } |
113 | 113 | ||
114 | // SplitText ... | 114 | // SplitText ... |
115 | func SplitText(s string, maxLen int) (lines []string) { | 115 | func SplitText(s string, maxLen int) (lines []string) { |
116 | runes := []rune(s) | 116 | runes := []rune(s) |
117 | 117 | ||
118 | i, start, sep, l := 0, 0, 0, 0 | 118 | i, start, sep, l := 0, 0, 0, 0 |
119 | for i = 0; i < len(runes); i++ { | 119 | for i = 0; i < len(runes); i++ { |
120 | c := runes[i] | 120 | c := runes[i] |
121 | 121 | ||
122 | if unicode.IsSpace(c) { | 122 | if unicode.IsSpace(c) { |
123 | sep = i | 123 | sep = i |
124 | } | 124 | } |
125 | 125 | ||
126 | if c == '\n' { | 126 | if c == '\n' { |
127 | if start != sep { | 127 | if start != sep { |
128 | lines = append(lines, string(runes[start:sep])) | 128 | lines = append(lines, string(runes[start:sep])) |
129 | } | 129 | } |
130 | start = i | 130 | start = i |
131 | sep = i | 131 | sep = i |
132 | l = 0 | 132 | l = 0 |
133 | } else if l >= maxLen { | 133 | } else if l >= maxLen { |
134 | if start != sep { | 134 | if start != sep { |
135 | lines = append(lines, string(runes[start:sep])) | 135 | lines = append(lines, string(runes[start:sep])) |
136 | sep = i | 136 | sep = i |
137 | start = i - 1 | 137 | start = i - 1 |
138 | l = 0 | 138 | l = 0 |
139 | } | 139 | } |
140 | } else { | 140 | } else { |
141 | l++ | 141 | l++ |
142 | } | 142 | } |
143 | } | 143 | } |
144 | if start != i-1 { | 144 | if start != i-1 { |
145 | lines = append(lines, string(runes[start:i-1])) | 145 | lines = append(lines, string(runes[start:i-1])) |
146 | } | 146 | } |
147 | 147 | ||
148 | return lines | 148 | return lines |
149 | } | 149 | } |
150 | 150 | ||
151 | func CutTextWithThreeDots(txt string, maxLen int) string { | ||
152 | if len(txt) < maxLen || len(txt) <= 3 { | ||
153 | return txt | ||
154 | } | ||
155 | |||
156 | return txt[:maxLen-3] + "..." | ||
157 | } | ||
158 | |||
151 | const threeDots = "\u2056\u2056\u2056" | 159 | const threeDots = "\u2056\u2056\u2056" |
152 | 160 | ||
153 | func LimitTextWithThreeDots(txt string, maxLen int) string { | 161 | func LimitTextWithThreeDots(txt string, maxLen int) string { |
154 | if len(txt) <= maxLen { | 162 | if len(txt) <= maxLen { |
155 | return txt | 163 | return txt |
156 | } | 164 | } |
157 | 165 | ||
158 | return txt[:maxLen] + threeDots | 166 | return txt[:maxLen] + threeDots |
159 | } | 167 | } |
160 | 168 | ||
169 | func LimitMSWordTextWithThreeDots(txt string, maxLen int) string { | ||
170 | if len(txt) <= maxLen { | ||
171 | return txt | ||
172 | } | ||
173 | |||
174 | return txt[:maxLen] + "..." | ||
175 | } | ||
176 | |||
161 | // SplitStringAtWholeWords ... | 177 | // SplitStringAtWholeWords ... |
162 | func SplitStringAtWholeWords(s string, maxLen int) (res []string) { | 178 | func SplitStringAtWholeWords(s string, maxLen int) (res []string) { |
163 | parts := strings.Split(s, " ") | 179 | parts := strings.Split(s, " ") |
164 | 180 | ||
165 | res = append(res, parts[0]) | 181 | res = append(res, parts[0]) |
166 | i := 0 | 182 | i := 0 |
167 | for j := 1; j < len(parts); j++ { | 183 | for j := 1; j < len(parts); j++ { |
168 | p := strings.TrimSpace(parts[j]) | 184 | p := strings.TrimSpace(parts[j]) |
169 | if len(p) > maxLen { | 185 | if len(p) > maxLen { |
170 | // TODO(marko): check if maxLen is >= 3 | 186 | // TODO(marko): check if maxLen is >= 3 |
171 | p = p[0 : maxLen-3] | 187 | p = p[0 : maxLen-3] |
172 | p += "..." | 188 | p += "..." |
173 | } | 189 | } |
174 | if len(res[i])+len(p)+1 <= maxLen { | 190 | if len(res[i])+len(p)+1 <= maxLen { |
175 | res[i] += " " + p | 191 | res[i] += " " + p |
176 | } else { | 192 | } else { |
177 | res = append(res, p) | 193 | res = append(res, p) |
178 | i++ | 194 | i++ |
179 | } | 195 | } |
180 | } | 196 | } |
181 | 197 | ||
182 | return res | 198 | return res |
183 | } | 199 | } |
184 | 200 |
timer.go
File was created | 1 | package webutility | |
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "time" | ||
6 | ) | ||
7 | |||
8 | // Timer ... | ||
9 | type Timer struct { | ||
10 | name string | ||
11 | running bool | ||
12 | started time.Time | ||
13 | stopped time.Time | ||
14 | lastDuration time.Duration | ||
15 | } | ||
16 | |||
17 | // NewTimer ... | ||
18 | func NewTimer(name string) *Timer { | ||
19 | t := &Timer{name: name} | ||
20 | t.Reset() | ||
21 | return t | ||
22 | } | ||
23 | |||
24 | // Start ... | ||
25 | func (t *Timer) Start() time.Time { | ||
26 | t.running = true | ||
27 | t.started = time.Now() | ||
28 | return t.started | ||
29 | } | ||
30 | |||
31 | // Stop ... | ||
32 | func (t *Timer) Stop() time.Duration { | ||
33 | t.running = false | ||
34 | t.stopped = time.Now() | ||
35 | t.lastDuration = t.stopped.Sub(t.started) | ||
36 | return t.lastDuration | ||
37 | } | ||
38 | |||
39 | // LastRunDuration ... | ||
40 | func (t *Timer) LastRunDuration() time.Duration { | ||
41 | return t.lastDuration | ||
42 | } | ||
43 | |||
44 | // Clear ... | ||
45 | func (t *Timer) Clear() { | ||
46 | t.started = time.Now() | ||
47 | t.stopped = time.Now() | ||
48 | t.lastDuration = 0 | ||
49 | } | ||
50 | |||
51 | // Reset ... | ||
52 | func (t *Timer) Reset() { | ||
53 | t.Stop() | ||
54 | t.Start() | ||
55 | } | ||
56 | |||
57 | // Elapsed ... | ||
58 | func (t *Timer) Elapsed() time.Duration { | ||
59 | if t.running { | ||
60 | return time.Now().Sub(t.started) | ||
61 | } | ||
62 | return 0 | ||
63 | } | ||
64 | |||
65 | // Print ... | ||
66 | func (t *Timer) Print(s string) { | ||
67 | status := "RUNNING" | ||
68 | if !t.running { | ||
69 | status = "STOPPED" | ||
70 | } | ||
71 | fmt.Printf("timer[%s][%s]: %v %s\n", t.name, status, t.Elapsed(), s) | ||
72 | } | ||
73 | |||
74 | // Lap ... | ||
75 | func (t *Timer) Lap(s string) { | ||
76 | t.Print(s) | ||
77 | t.Reset() | ||
78 | } | ||
79 |