I'm trying to scan some folders and sort them to find the highest version number. {"10.1","9.6","7.2"} and then build a path. However, What I get has [] brackets in the path and I need to get rid of those.
Here's what I'm getting:
C:\Program Files\PostgreSQL\[10.1]\bin\psql.exe
root := "C:/Program Files/PostgreSQL"
files, err := ioutil.ReadDir(root)
if err != nil {
return "", err
}
folders := []float64{}
for _, f := range files {
if f.IsDir() {
if converted, err := strconv.ParseFloat(f.Name(),64); err == nil {
folders = append(folders, converted)
}
}
}
sort.Float64s(folders)
log.Println(folders[len(folders)-1:])
highestVersion := fmt.Sprintf("%v",folders[len(folders)-1:])
execPath = filepath.Join(root, highestVersion, "bin", "psql.exe")
log.Println(execPath)
The issues are on this line:
highestVersion := fmt.Sprintf("%v",folders[len(folders)-1:])
The %v format specifier, as some people have mentioned, is shorthand for "value". Now let's look at your value:
folders[len(folders)-1:]
What you are saying here is, "take a slice from folders starting at len(folders-1)". Your variable is a slice that only contains the last item in folders.
Slices are printed by using brackets around the values, and since you have one value, it prints the value surrounded by square brackets.
If you want to print just the float contained in that location, you should remove the colon as specified in a comment. I would recommend printing using the fmt verb %f or %g, depending on your use case.
More information can be found in the pkg/fmt docs about what verbs are available to printf and other related functions.
One possible approach would be to use a regular expression to ensure that each path has the expected format and to extract the version number as a float via a submatch (matching group), then sort the path strings by their floating point version number value and return the highest one.
For example:
func main() {
paths := []string{
`C:\Program Files\PostgreSQL\[1.2]\bin\psql.exe`,
`C:\Program Files\PostgreSQL\[9.6]\bin\psql.exe`,
`C:\Program Files\PostgreSQL\[10.1]\bin\psql.exe`,
`C:\Program Files\PostgreSQL\[7.2]\bin\psql.exe`,
}
sort.Slice(paths, func(i, j int) bool {
return parseVersion(paths[i]) >= parseVersion(paths[j])
})
fmt.Printf("OK: highest version path = %s\n", paths[0])
// OK: highest version path = C:\Program Files\PostgreSQL\[10.1]\bin\psql.exe
}
var re = regexp.MustCompile(`C:\\Program Files\\PostgreSQL\\\[(\d+\.\d+)\]\\bin\\psql.exe`)
func parseVersion(s string) float32 {
match := re.FindStringSubmatch(s)
if match == nil {
panic(fmt.Errorf("invalid path %q", s))
}
version, err := strconv.ParseFloat(match[1], 32)
if err != nil {
panic(err)
}
return float32(version)
}
Of course, you could modify the path regular expression to match different location patterns if that matters for your use case.
Related
write now i have a huge string which i get from 250-300 characters and i'm writing to file using
file, err := ioutil.TempFile("/Downloads", "*.txt")
if err != nil {
log.Fatal(err)
}
file.Write(mystring)
This writes everything in one line, but is there a way to pad the lines so that automatically after 76 char, we get onto new line.
found a solution which does exactly the above requirment.
made it a generic solution to split based on "n" length and whatever delimeter is required.
you can try it in the playground if you wish (https://play.golang.org/p/5ZHCC_Z5uqc)
func insertNth(s string, n int) string {
var buffer bytes.Buffer
var n_1 = n - 1
var l_1 = len(s) - 1
for i, rune := range s {
buffer.WriteRune(rune)
if i%n == n_1 && i != l_1 {
buffer.WriteRune('\n')
}
}
return buffer.String()
}
https://play.golang.org/p/5ZHCC_Z5uqc
Did some digging in and actually found it not that difficult, posted my solution above.
I have written a small constant number expression evaluator. I’m running go-fuzz on it, but it can only detect crashes.
I would like to test that expression considered invalid by my program are really invalid, and valid expression are really valid and yield a correct result.
How could I do that in Go ?
I looked into existing packages like this one, but it allows much more operations and types than I currently support. It thus can’t use it for validation.
The values that I handle are int and float64, and the operations are | & ^ ~(inverse) + - * / %. Also I support the same type of number literals as Go.
I did manual checks but this doesn’t bring me very far. I would need to check the result against another expression evaluator.
You can use go/types and co.:
Constant folding computes the exact constant value (constant.Value)
for every expression (ast.Expr) that is a compile-time constant. Use
Info.Types[expr].Value for the results of constant folding.
I'll use Info.Defs instead of Info.Types as that's simpler for this case, IMO at least.
expr := "float64(20)/6.8"
// parse the expression as part of a file so that it can be type checked
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "x.go", "package x\nconst K = "+expr, 0)
if err != nil {
panic(err)
}
// type check the file so that the constant value gets computed
info := types.Info{Defs: map[*ast.Ident]types.Object{}}
conf := types.Config{}
if _, err := conf.Check("x", fset, []*ast.File{f}, &info); err != nil {
panic(err)
}
// get the value (as string)
for id, obj := range info.Defs {
if id.Name == "K" {
v := obj.(*types.Const).Val()
fmt.Println(v.String())
}
}
https://play.golang.org/p/h2e0No2F1CY
How do I append output from a twitter search to the field Data in the SearchTwitterOutput{} struct.
Thanks!
I am using a twitter library to search twitter base on a query input. The search returns an array of strings(I believe), I am able to fmt.println the data but I need the data as a struct.
type SearchTwitterOutput struct {
Data string
}
func (SearchTwitter) execute(input SearchTwitterInput) (*SearchTwitterOutput, error) {
credentials := Credentials{
AccessToken: input.AccessToken,
AccessTokenSecret: input.AccessTokenSecret,
ConsumerKey: input.ConsumerKey,
ConsumerSecret: input.ConsumerSecret,
}
client, err := GetUserClient(&credentials)
if err != nil {
return nil, err
}
// search through the tweet and returns a
search, _ , err := client.Search.Tweets(&twitter.SearchTweetParams{
Query: input.Text,
})
if err != nil {
println("PANIC")
panic(err.Error())
return &SearchTwitterOutput{}, err
}
for k, v := range search.Statuses {
fmt.Printf("Tweet %d - %s\n", k, v.Text)
}
return &SearchTwitterOutput{
Data: "test", //data is a string for now it can be anything
}, nil
}
//Data field is a string type for now it can be anything
//I use "test" as a placeholder, bc IDK...
Result from fmt.Printf("Tweet %d - %s\n", k, v.Text):
Tweet 0 - You know I had to do it to them! #JennaJulien #Jenna_Marbles #juliensolomita #notjulen Got my first hydroflask ever…
Tweet 1 - RT #brenna_hinshaw: I was in J2 today and watched someone fill their hydroflask with vanilla soft serve... what starts here changes the wor…
Tweet 2 - I miss my hydroflask :(
This is my second week working with go and new to development. Any help would be great.
It doesn't look like the client is just returning you a slice of strings. The range syntax you're using (for k, v := range search.Statuses) returns two values for each iteration, the index in the slice (in this case k), and the object from the slice (in this case v). I don't know the type of search.Statuses - but I know that strings don't have a .Text field or method, which is how you're printing v currently.
To your question:
Is there any particular reason to return just a single struct with a Data field rather than directly returning the output of the twitter client?
Your function signature could look like this instead:
func (SearchTwitter) execute(input SearchTwitterInput) ([]<client response struct>, error)
And then you could operate on the text in those objects in wherever this function was called.
If you're dead-set on placing the data in your own struct, you could return a slice of them ([]*SearchTwitterOutput), in which case you could build a single SearchTwitterOutput in the for loop you're currently printing the tweets in and append it to the output list. That might look like this:
var output []*SearchTwitterOutput
for k, v := range search.Statuses {
fmt.Printf("Tweet %d - %s\n", k, v.Text)
output = append(output, &SearchTwitterOutput{
Data: v.Text,
})
}
return output, nil
But if your goal really is to return all of the results concatenated together and placed inside a single struct, I would suggest building a slice of strings (containing the text you want), and then joining them with the delimiter of your choosing. Then you could place the single output string in your return object, which might look something like this:
var outputStrings []string
for k, v := range search.Statuses {
fmt.Printf("Tweet %d - %s\n", k, v.Text)
outputStrings = append(outputStrings, v.Text)
}
output = strings.Join(outputStrings, ",")
return &SearchTwitterOutput{
Data: output,
}, nil
Though I would caution, it might be tricky to find a delimiter that will never show up in a tweet..
I have two structs, like so:
// init a struct for a single item
type Cluster struct {
Name string
Path string
}
// init a grouping struct
type Clusters struct {
Cluster []Cluster
}
What I want to do is append to new items to the clusters struct. So I wrote a method, like so:
func (c *Clusters) AddItem(item Cluster) []Cluster {
c.Cluster = append(c.Cluster, item)
return c.Cluster
}
The way my app works, I loop through some directories then append the name of the final directory and it's path. I have a function, that is called:
func getClusters(searchDir string) Clusters {
fileList := make([]string, 0)
//clusterName := make([]string, 0)
//pathName := make([]string, 0)
e := filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error {
fileList = append(fileList, path)
return err
})
if e != nil {
log.Fatal("Error building cluster list: ", e)
}
for _, file := range fileList {
splitFile := strings.Split(file, "/")
// get the filename
fileName := splitFile[len(splitFile)-1]
if fileName == "cluster.jsonnet" {
entry := Cluster{Name: splitFile[len(splitFile)-2], Path: strings.Join(splitFile[:len(splitFile)-1], "/")}
c.AddItem(entry)
}
}
Cluster := []Cluster{}
c := Clusters{Cluster}
return c
}
The problem here is that I don't know the correct way to do this.
Currently, I'm getting:
cmd/directories.go:41:4: undefined: c
So I tried moving this:
Cluster := []Cluster{}
c := Clusters{Cluster}
Above the for loop - range. The error I get is:
cmd/directories.go:43:20: Cluster is not a type
What am I doing wrong here?
The error is in the loop where you are calling AddItem function on Cluster method receiver which is not defined inside getClusters function. Define Cluster struct before for loop and then call the function c.AddItem as defined below:
func getClusters(searchDir string) Clusters {
fileList := make([]string, 0)
fileList = append(fileList, "f1", "f2", "f3")
ClusterData := []Cluster{}
c := Clusters{Cluster: ClusterData} // change the struct name passed to Clusters struct
for _, file := range fileList {
entry := Cluster{Name: "name" + file, Path: "path" + file}
c.AddItem(entry)
}
return c
}
you have defined the same struct name to Clusters struct that's why the error
cmd/directories.go:43:20: Cluster is not a type
Checkout working code on Go playground
In Golang Composite literal is defined as:
Composite literals construct values for structs, arrays, slices, and maps and create a new value each time they are evaluated. They
consist of the type of the literal followed by a brace-bound list of
elements. Each element may optionally be preceded by a corresponding
key.
Also Have a look on struct literals section defined in above link for Compositeliterals to get more description.
You need to define c before entering the loop in which you use it.
The Cluster is not a type error is due to using the same Cluster name as the type and the variable, try using a different variable name.
clusterArr := []Cluster{}
c := Clusters{clusterArr}
for _, file := range fileList {
....
}
Say we have two paths:
c:\foo\bar\baz and c:\foo\bar
Is there any package/method that will help me determine if one is a subdirectory of another? I am looking at a cross-platform option.
You could try and use path.filepath.Rel():
func Rel(basepath, targpath string) (string, error)
Rel returns a relative path that is lexically equivalent to targpath when joined to basepath with an intervening separator.
That is, Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself
That means Rel("c:\foo\bar", "c:\foo\bar\baz") should be baz, meaning a subpath completely included in c:\foo\bar\baz, and without any '../'.
The same would apply for unix paths.
That would make c:\foo\bar\baz a subdirectory of c:\foo\bar.
I haven't found a reliable solution for all types of paths, but the best you can get is by using filepath.Rel as VonC suggested.
It works if both filepaths are either absolute or relative (mixing is not allowed) and works on both Windows and Linux:
func SubElem(parent, sub string) (bool, error) {
up := ".." + string(os.PathSeparator)
// path-comparisons using filepath.Abs don't work reliably according to docs (no unique representation).
rel, err := filepath.Rel(parent, sub)
if err != nil {
return false, err
}
if !strings.HasPrefix(rel, up) && rel != ".." {
return true, nil
}
return false, nil
}
Absolute windows paths that start with a drive letter will require an additional check though.
You can use the function path.filepath.Match()
Match reports whether name matches the shell file name pattern.
For example:
pattern := "C:\foo\bar" + string(filepath.Separator) + "*"
matched, err := filepath.Match(pattern, "C:\foo\bar\baz")
Where matched should be true.
If you first canonicalize both paths by calling filepath.EvalSymlinks() and filepath.Abs() on them, you can simply append a '/' to each one, since the UNIX kernel itself forbids a '/' within a path component. At this point you can simply use strings.HasPrefix() on the two paths, in either order.
Try this code. This checks if either is a sub-directory of the other. Try changing values of both base and path and the results should be valid.
package main
import (
"fmt"
"path/filepath"
"strings"
)
func main() {
base := "/b/c/"
path := "/a/b/c/d"
if len(base) > len(path) {
base, path = path, base
}
rel, err := filepath.Rel(base, path)
fmt.Printf("Base %q: Path %q: Rel %q Err %v\n", base, path, rel, err)
if err != nil {
fmt.Println("PROCEED")
return
}
if strings.Contains(rel, "..") {
fmt.Println("PROCEED")
return
}
fmt.Println("DENY")
}