I'd like to write a method that will populate a Go Language array with the common timezones that are accepted by the time.Format() call, for use in an HTML template (Form select to allow them to read and choose their timezone). Is there a common way to do this?
To get a list of time zones, you can use something like:
package main
import (
"fmt"
"io/ioutil"
"strings"
)
var zoneDirs = []string{
// Update path according to your OS
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
}
var zoneDir string
func main() {
for _, zoneDir = range zoneDirs {
ReadFile("")
}
}
func ReadFile(path string) {
files, _ := ioutil.ReadDir(zoneDir + path)
for _, f := range files {
if f.Name() != strings.ToUpper(f.Name()[:1]) + f.Name()[1:] {
continue
}
if f.IsDir() {
ReadFile(path + "/" + f.Name())
} else {
fmt.Println((path + "/" + f.Name())[1:])
}
}
}
output:
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
Africa/Asmera
Africa/Bamako
Africa/Bangui
...
Go's time pkg uses a timezone database.
You can load a timezone location like this:
loc, err := time.LoadLocation("America/Chicago")
if err != nil {
// handle error
}
t := time.Now().In(loc)
The Format function is not related to setting the time zone, this function takes a fixed reference time that allows you to format the date how you would like. Take a look at the time pkg docs.
For instance:
fmt.Println(t.Format("MST")) // outputs CST
Here is a running example
Here is an example: https://play.golang.org/p/KFGQiW5A1P-
package main
import (
"fmt"
"io/ioutil"
"strings"
"unicode"
)
func main() {
fmt.Println(GetOsTimeZones())
}
func GetOsTimeZones() []string {
var zones []string
var zoneDirs = []string{
// Update path according to your OS
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
}
for _, zd := range zoneDirs {
zones = walkTzDir(zd, zones)
for idx, zone := range zones {
zones[idx] = strings.ReplaceAll(zone, zd+"/", "")
}
}
return zones
}
func walkTzDir(path string, zones []string) []string {
fileInfos, err := ioutil.ReadDir(path)
if err != nil {
return zones
}
isAlpha := func(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) {
return false
}
}
return true
}
for _, info := range fileInfos {
if info.Name() != strings.ToUpper(info.Name()[:1])+info.Name()[1:] {
continue
}
if !isAlpha(info.Name()[:1]) {
continue
}
newPath := path + "/" + info.Name()
if info.IsDir() {
zones = walkTzDir(newPath, zones)
} else {
zones = append(zones, newPath)
}
}
return zones
}
Related
I have a struct which contains a type based on an enum. I am trying to render it to a user friendly string. Here's minimum viable code:
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
type Job struct {
Engine Engine `json:"Engine" yaml:"Engine"`
}
//go:generate stringer -type=Engine --trimprefix=Engine
type Engine int
const (
engineUnknown Engine = iota // must be first
EngineDocker
engineDone // must be last
)
func main() {
j := Job{Engine: EngineDocker}
fmt.Printf("%+v\n\n", j)
out, _ := yaml.Marshal(j)
fmt.Println(string(out))
}
Here's the generated code:
// Code generated by "stringer -type=Engine --trimprefix=Engine"; DO NOT EDIT.
package main
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[engineUnknown-0]
_ = x[EngineDocker-1]
_ = x[engineDone-2]
}
const _Engine_name = "engineUnknownDockerengineDone"
var _Engine_index = [...]uint8{0, 13, 19, 29}
func (i Engine) String() string {
if i < 0 || i >= Engine(len(_Engine_index)-1) {
return "Engine(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Engine_name[_Engine_index[i]:_Engine_index[i+1]]
}
Here's the output:
{Engine:1}
Engine: 1
Here's what I'd like the output to be:
{Engine:Docker}
Engine: Docker
I thought the String() in the generated file would accomplish this. Is there any way to do this? Thanks!
yaml marshaler doesn't use String method. Instead YAML uses encoding.TextMarshaler and encoding.TextUnmarshaler interfaces. Actually, all other codec schemes - JSON, XML, TOML, etc. - use those interfaces to read/write the values. So, if you implement those methods for your type, you will receive all other codecs for free.
Here is an example how to make a human-readable encoding for your enum: https://go.dev/play/p/pEcBmAM-oZJ
type Engine int
const (
engineUnknown Engine = iota // must be first
EngineDocker
engineDone // must be last
)
var engineNames []string
var engineNameToValue map[string]Engine
func init() {
engineNames = []string{"Unknown", "Docker"}
engineNameToValue = make(map[string]Engine)
for i, name := range engineNames {
engineNameToValue[strings.ToLower(name)] = Engine(i)
}
}
func (e Engine) String() string {
if e < 0 || int(e) >= len(engineNames) {
panic(fmt.Errorf("Invalid engine code: %d", e))
}
return engineNames[e]
}
func ParseEngine(text string) (Engine, error) {
i, ok := engineNameToValue[strings.ToLower(text)]
if !ok {
return engineUnknown, fmt.Errorf("Invalid engine name: %s", text)
}
return i, nil
}
func (e Engine) MarshalText() ([]byte, error) {
return []byte(e.String()), nil
}
func (e *Engine) UnmarshalText(text []byte) (err error) {
name := string(text)
*e, err = ParseEngine(name)
return
}
How it works:
func main() {
j := Job{Engine: EngineDocker}
fmt.Printf("%#v\n\n", j)
out, err := yaml.Marshal(j)
if err != nil {
panic(err)
}
fmt.Printf("YAML: %s\n", string(out))
var jj Job
err = yaml.Unmarshal(out, &jj)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n\n", jj)
// == JSON ==
out, err = json.Marshal(j)
if err != nil {
panic(err)
}
fmt.Printf("JSON: %s\n", string(out))
var jjs Job
err = json.Unmarshal(out, &jjs)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n\n", jjs)
}
the output
main.Job{Engine:1}
YAML: Engine: Docker
main.Job{Engine:1}
JSON: {"Engine":"Docker"}
main.Job{Engine:1}
See? It writes and reads strings to both YAML and JSON without any extra effort.
Is there a way to list out all functions that uses/returns a specific type?
For example: I'm interested to use the following function.
func ListenAndServe(addr string, handler Handler) error
How can I find out all functions (across all Go packages) that can return a Handler?
I'd write an analysis tool using the x/tools/go/analysis framework. Here's a rough sketch that you can run on any module (it uses go/packages underneath so it fully supports modules):
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/singlechecker"
)
var RtAnalysis = &analysis.Analyzer{
Name: "rtanalysis",
Doc: "finds functions by return type",
Run: run,
}
func main() {
singlechecker.Main(RtAnalysis)
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
if funcTy, ok := n.(*ast.FuncType); ok {
if funcTy.Results != nil {
for _, fl := range funcTy.Results.List {
if tv, ok := pass.TypesInfo.Types[fl.Type]; ok {
if tv.Type.String() == "net/http.Handler" {
ns := nodeString(funcTy, pass.Fset)
fmt.Printf("%s has return of type net/http.Handler\n", ns)
}
}
}
}
}
return true
})
}
return nil, nil
}
// nodeString formats a syntax tree in the style of gofmt.
func nodeString(n ast.Node, fset *token.FileSet) string {
var buf bytes.Buffer
format.Node(&buf, fset, n)
return buf.String()
}
I'd like to write a method that will populate a Go Language array with the common timezones that are accepted by the time.Format() call, for use in an HTML template (Form select to allow them to read and choose their timezone). Is there a common way to do this?
To get a list of time zones, you can use something like:
package main
import (
"fmt"
"io/ioutil"
"strings"
)
var zoneDirs = []string{
// Update path according to your OS
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
}
var zoneDir string
func main() {
for _, zoneDir = range zoneDirs {
ReadFile("")
}
}
func ReadFile(path string) {
files, _ := ioutil.ReadDir(zoneDir + path)
for _, f := range files {
if f.Name() != strings.ToUpper(f.Name()[:1]) + f.Name()[1:] {
continue
}
if f.IsDir() {
ReadFile(path + "/" + f.Name())
} else {
fmt.Println((path + "/" + f.Name())[1:])
}
}
}
output:
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
Africa/Asmera
Africa/Bamako
Africa/Bangui
...
Go's time pkg uses a timezone database.
You can load a timezone location like this:
loc, err := time.LoadLocation("America/Chicago")
if err != nil {
// handle error
}
t := time.Now().In(loc)
The Format function is not related to setting the time zone, this function takes a fixed reference time that allows you to format the date how you would like. Take a look at the time pkg docs.
For instance:
fmt.Println(t.Format("MST")) // outputs CST
Here is a running example
Here is an example: https://play.golang.org/p/KFGQiW5A1P-
package main
import (
"fmt"
"io/ioutil"
"strings"
"unicode"
)
func main() {
fmt.Println(GetOsTimeZones())
}
func GetOsTimeZones() []string {
var zones []string
var zoneDirs = []string{
// Update path according to your OS
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
}
for _, zd := range zoneDirs {
zones = walkTzDir(zd, zones)
for idx, zone := range zones {
zones[idx] = strings.ReplaceAll(zone, zd+"/", "")
}
}
return zones
}
func walkTzDir(path string, zones []string) []string {
fileInfos, err := ioutil.ReadDir(path)
if err != nil {
return zones
}
isAlpha := func(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) {
return false
}
}
return true
}
for _, info := range fileInfos {
if info.Name() != strings.ToUpper(info.Name()[:1])+info.Name()[1:] {
continue
}
if !isAlpha(info.Name()[:1]) {
continue
}
newPath := path + "/" + info.Name()
if info.IsDir() {
zones = walkTzDir(newPath, zones)
} else {
zones = append(zones, newPath)
}
}
return zones
}
Is there a native way for inplace url parameters in native Go?
For Example, if I have a URL: http://localhost:8080/blob/123/test I want to use this URL as /blob/{id}/test.
This is not a question about finding go libraries. I am starting with the basic question, does go itself provide a basic facility to do this natively.
There is no built in simple way to do this, however, it is not hard to do.
This is how I do it, without adding a particular library. It is placed in a function so that you can invoke a simple getCode() function within your request handler.
Basically you just split the r.URL.Path into parts, and then analyse the parts.
// Extract a code from a URL. Return the default code if code
// is missing or code is not a valid number.
func getCode(r *http.Request, defaultCode int) (int, string) {
p := strings.Split(r.URL.Path, "/")
if len(p) == 1 {
return defaultCode, p[0]
} else if len(p) > 1 {
code, err := strconv.Atoi(p[0])
if err == nil {
return code, p[1]
} else {
return defaultCode, p[1]
}
} else {
return defaultCode, ""
}
}
Well, without external libraries you can't, but may I recommend two excellent ones:
httprouter - https://github.com/julienschmidt/httprouter - is extremely fast and very lightweight. It's faster than the standard library's router, and it creates 0 allocations per call, which is great in a GCed language.
Gorilla Mux - http://www.gorillatoolkit.org/pkg/mux -
Very popular, nice interface, nice community.
Example usage of httprouter:
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}
func main() {
router := httprouter.New()
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8080", router))
}
What about trying using regex, and find a named group in your url, like playground:
package main
import (
"fmt"
"net/url"
"regexp"
)
var myExp = regexp.MustCompile(`/blob/(?P<id>\d+)/test`) // use (?P<id>[a-zA-Z]+) if the id is alphapatic
func main() {
s := "http://localhost:8080/blob/123/test"
u, err := url.Parse(s)
if err != nil {
panic(err)
}
fmt.Println(u.Path)
match := myExp.FindStringSubmatch(s) // or match := myExp.FindStringSubmatch(u.Path)
result := make(map[string]string)
for i, name := range myExp.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
fmt.Printf("id: %s\n", result["id"])
}
output
/blob/123/test
id: 123
Below full code to use it with url, that is receiving http://localhost:8000/hello/John/58 and returning http://localhost:8000/hello/John/58:
package main
import (
"fmt"
"net/http"
"regexp"
"strconv"
)
var helloExp = regexp.MustCompile(`/hello/(?P<name>[a-zA-Z]+)/(?P<age>\d+)`)
func hello(w http.ResponseWriter, req *http.Request) {
match := helloExp.FindStringSubmatch(req.URL.Path)
if len(match) > 0 {
result := make(map[string]string)
for i, name := range helloExp.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
if _, err := strconv.Atoi(result["age"]); err == nil {
fmt.Fprintf(w, "Hello, %v year old named %s!", result["age"], result["name"])
} else {
fmt.Fprintf(w, "Sorry, not accepted age!")
}
} else {
fmt.Fprintf(w, "Wrong url\n")
}
}
func main() {
http.HandleFunc("/hello/", hello)
http.ListenAndServe(":8090", nil)
}
How about writing your own url generator (extend net/url a little bit) as below.
// --- This is how does it work like --- //
url, _ := rest.NewURLGen("http", "stack.over.flow", "1234").
Pattern(foo/:foo_id/bar/:bar_id).
ParamQuery("foo_id", "abc").
ParamQuery("bar_id", "xyz").
ParamQuery("page", "1").
ParamQuery("offset", "5").
Do()
log.Printf("url: %s", url)
// url: http://stack.over.flow:1234/foo/abc/bar/xyz?page=1&offset=5
// --- Your own url generator would be like below --- //
package rest
import (
"log"
"net/url"
"strings"
"straas.io/base/errors"
"github.com/jinzhu/copier"
)
// URLGen generates request URL
type URLGen struct {
url.URL
pattern string
paramPath map[string]string
paramQuery map[string]string
}
// NewURLGen new a URLGen
func NewURLGen(scheme, host, port string) *URLGen {
h := host
if port != "" {
h += ":" + port
}
ug := URLGen{}
ug.Scheme = scheme
ug.Host = h
ug.paramPath = make(map[string]string)
ug.paramQuery = make(map[string]string)
return &ug
}
// Clone return copied self
func (u *URLGen) Clone() *URLGen {
cloned := &URLGen{}
cloned.paramPath = make(map[string]string)
cloned.paramQuery = make(map[string]string)
err := copier.Copy(cloned, u)
if err != nil {
log.Panic(err)
}
return cloned
}
// Pattern sets path pattern with placeholder (format `:<holder_name>`)
func (u *URLGen) Pattern(pattern string) *URLGen {
u.pattern = pattern
return u
}
// ParamPath builds path part of URL
func (u *URLGen) ParamPath(key, value string) *URLGen {
u.paramPath[key] = value
return u
}
// ParamQuery builds query part of URL
func (u *URLGen) ParamQuery(key, value string) *URLGen {
u.paramQuery[key] = value
return u
}
// Do returns final URL result.
// The result URL string is possible not escaped correctly.
// This is input for `gorequest`, `gorequest` will handle URL escape.
func (u *URLGen) Do() (string, error) {
err := u.buildPath()
if err != nil {
return "", err
}
u.buildQuery()
return u.String(), nil
}
func (u *URLGen) buildPath() error {
r := []string{}
p := strings.Split(u.pattern, "/")
for i := range p {
part := p[i]
if strings.Contains(part, ":") {
key := strings.TrimPrefix(p[i], ":")
if val, ok := u.paramPath[key]; ok {
r = append(r, val)
} else {
if i != len(p)-1 {
// if placeholder at the end of pattern, it could be not provided
return errors.Errorf("placeholder[%s] not provided", key)
}
}
continue
}
r = append(r, part)
}
u.Path = strings.Join(r, "/")
return nil
}
func (u *URLGen) buildQuery() {
q := u.URL.Query()
for k, v := range u.paramQuery {
q.Set(k, v)
}
u.RawQuery = q.Encode()
}
With net/http the following would trigger when calling localhost:8080/blob/123/test
http.HandleFunc("/blob/", yourHandlerFunction)
Then inside yourHandlerFunction, manually parse r.URL.Path to find 123.
Note that if you don't add a trailing / it won't work. The following would only trigger when calling localhost:8080/blob:
http.HandleFunc("/blob", yourHandlerFunction)
As of 19-Sep-22, with go version 1.19, instance of http.request URL has a method called Query, which will return a map, which is a parsed query string.
func helloHandler(res http.ResponseWriter, req *http.Request) {
// when request URL is `http://localhost:3000/?first=hello&second=world`
fmt.Println(req.URL.Query()) // outputs , map[second:[world] first:[hello]]
res.Write([]byte("Hello World Web"))
}
No way without standard library. Why you don't want to try some library? I think its not so hard to use it, just go get bla bla bla
I use Beego. Its MVC style.
how about a simple utility function ?
func withURLParams(u url.URL, param, val string) url.URL{
u.Path = strings.ReplaceAll(u.Path, param, val)
return u
}
you can use it like this:
u, err := url.Parse("http://localhost:8080/blob/:id/test")
if err != nil {
return nil, err
}
u := withURLParams(u, ":id","123")
// now u.String() is http://localhost:8080/blob/123/test
If you need a framework and you think it will be slow because it's 'bigger' than a router or net/http, then you 're wrong.
Iris is the fastest go web framework that you will ever find, so far according to all benchmarks.
Install by
go get gopkg.in/kataras/iris.v6
Django templates goes easy with iris:
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view" // <-----
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // you can choose gorillamux too
app.Adapt(view.Django("./templates", ".html")) // <-----
// RESOURCE: http://127.0.0.1:8080/hi
// METHOD: "GET"
app.Get("/hi", hi)
app.Listen(":8080")
}
func hi(ctx *iris.Context){
ctx.Render("hi.html", iris.Map{"Name": "iris"})
}
I'm parsing through the triples of the Freebase RDF compressed and streaming with the XML package in Golang. However, I'm getting an out of memory error.
Do I have to garbage-collect? How can I do that? How can I clear the memory after I'm doing writing that triple to the XML file?
Here's my code: http://play.golang.org/p/dWvbtcs7wy
package main
import(
"bufio"
"flag"
"fmt"
"io"
"net/url"
"os"
"regexp"
"strings"
)
var inputFile = flag.String("infile", "freebase-rdf", "Input file path")
var filter, _ = regexp.Compile("^file:.*|^talk:.*|^special:.*|^wikipedia:.*|^wiktionary:.*|^user:.*|^user_talk:.*")
type Redirect struct {
Title string `xml:"title,attr"`
}
type Page struct {
Title string `xml:"title"`
Abstract string `xml:""`
}
func CanonicaliseTitle(title string) string{
can := strings.ToLower(title)
can = strings.Replace(can, " ", "_", -1)
can = url.QueryEscape(can)
return can
}
func convertFreebaseId(uri string) string{
if strings.HasPrefix(uri, "<") && strings.HasSuffix(uri, ">") {
var id = uri[1 : len(uri)-1]
id = strings.Replace(id, "http://rdf.freebase.com/ns", "", -1)
id = strings.Replace(id, ".", "/", -1)
return id
}
return uri
}
func parseTriple(line string) (string, string, string){
var parts = strings.Split(line, "\t")
subject := convertFreebaseId(parts[0])
predicate := convertFreebaseId(parts[1])
object := convertFreebaseId(parts[2])
return subject, predicate, object
}
var (
validRegexp = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9_-]*$")
englishRegexp = regexp.MustCompile("#en")
)
func validTitle(content []string) bool{
for _, v := range content{
if !englishRegexp.MatchString(v) && len(v) > 1 && strings.Index(v, "[]") != -1{
}
}
return true
}
func validText(content []string) bool{
for _, v := range content{
if !validRegexp.MatchString(v) && len(v) > 1 && strings.Index(v, "[]") != -1{
return false
}
}
return true
}
func processTopic(id string, properties map[string][]string, file io.Writer){
if validTitle(properties["/type/object/name"]) && validText(properties["/common/document/text"]){
fmt.Fprintf(file, "<card>\n")
fmt.Fprintf(file, "<title>\"%s\"</title>\n", properties["/type/object/name"])
fmt.Fprintf(file, "<image>\"%s/%s\"</image>\n", "https://usercontent.googleapis.com/freebase/v1/image", id)
fmt.Fprintf(file, "<text>\"%s\"</text>\n", properties["/common/document/text"])
fmt.Fprintf(file, "<facts>\n")
for k, v := range properties{
for _, value := range v{
fmt.Fprintf(file, "<fact property=\"%s\">%s</fact>\n", k, value)
}
}
fmt.Fprintf(file, "</facts>\n")
fmt.Fprintf(file, "</card>\n")
}
}
func main(){
var current_mid = ""
current_topic := make(map[string][]string)
f, err := os.Open(*inputFile)
if err != nil {
fmt.Println(err)
return
}
r := bufio.NewReader(f)
xmlFile, _ := os.Create("freebase.xml")
line, err := r.ReadString('\n')
for err == nil{
subject, predicate, object := parseTriple(line)
if subject == current_mid{
current_topic[predicate] = append(current_topic[predicate], object)
}else if len(current_mid) > 0{
processTopic(current_mid, current_topic, xmlFile)
current_topic = make(map[string][]string)
}
current_mid = subject
line, err = r.ReadString('\n')
}
processTopic(current_mid, current_topic, xmlFile)
if err != io.EOF {
fmt.Println(err)
return
}
}
I'm not sure that this is your problem, although reading your code it seems you're not leaking anything - but you can tune GC behavior a bit with SetGCPercent() http://golang.org/pkg/runtime/debug/#SetGCPercent
According to TFM, a collection is triggered when the ratio of freshly allocated data to live data remaining after the previous collection reaches this percentage.. The default rate is 100%, meaning for programs that make lots of small allocations and hold lots of RAM, the overhead can be huge. I had an HTTP cache take up over 200% the cache size once. Try tuning the percentage to somewhere around 10% and see if it helps.