How to rewrite wiki links in Golang? - go

Been stuck trying to rewrite some text in Golang: http://play.golang.org/p/0hoXx7qA0b5
How do I match several [[]] links in a text string?
log.Printf("match: %+v", match) doesn't show the log Group matches clearly. Am I missing something to help me work with matches, so I know whether it's a link with a title or not.
Is there a better approach than using regexp?

It seems that (.*) is a greedy match hence you should try limiting the first group. Based on your sample input, the |about is optional.
var re = regexp.MustCompile(`\[\[([^|]*)(?:\|(.*))?\]\]`)
func relink(input string) string {
var reform []string
for _, match := range re.FindAllStringSubmatch(input, -1) {
name, short := match[1], match[2]
if short == "" {
short = strings.ToLower(name)
}
reform = append(reform, fmt.Sprintf("[%s](%s)", name, short))
}
return strings.Join(reform, "\n")
}
Playground

Related

Alternative to using strings.Builder in conjunction with fmt.Sprintf

I am learning about the strings package in Go and I am trying to build up a simple error message.
I read that strings.Builder is a very eficient way to join strings, and that fmt.Sprintf lets me do some string interpolation.
With that said, I want to understand the best way to join a lot of strings together. For example here is what I create:
func generateValidationErrorMessage(err error) string {
errors := []string{}
for _, err := range err.(validator.ValidationErrors) {
var b strings.Builder
b.WriteString(fmt.Sprintf("[%s] failed validation [%s]", err.Field(), err.ActualTag()))
if err.Param() != "" {
b.WriteString(fmt.Sprintf("[%s]", err.Param()))
}
errors = append(errors, b.String())
}
return strings.Join(errors, "; ")
}
Is there another/better way to do this? Is using s1 + s2 considered worse?
You can use fmt to print directly to the strings.Builder. Use fmt.Fprintf(&builder, "format string", args).
The fmt functions beginning with Fprint..., meaning "file print", allow you to print to an io.Writer such as a os.File or strings.Builder.
Also, rather than using multiple builders and joining all their strings at the end, just use a single builder and keep writing to it. If you want to add a separator, you can do so easily within the loop:
var builder strings.Builder
for i, v := range values {
if i > 0 {
// unless this is the first item, add the separator before it.
fmt.Fprint(&builder, "; ")
}
fmt.Fprintf(&builder, "some format %v", v)
}
var output = builder.String()

is there a way to word wrap/pad one sentence to multiple sentences in go

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.

Not being able to concat slices

I have the following code, which is returning 4 slices, and I'd like to make it one.
var postArray []string
if strings.Contains(s.Text(), "k") || greaterThan(voteValue) {
postArray = append(postArray, s.Text())
}
for _, p := range postArray {
postArray = append(postArray, p...)
fmt.Println(reflect.TypeOf(p))
}
fmt.Println(postArray)
I'm not posting the whole code because I guess that it won't be needed, the slices returned are these ones:
[31.8k], [3151], [50.5k], [8111], but I'd like to get [31.8k 3151 50.5k 8111]. The p variable is a string, so I get the following error:
cannot use p (type string) as type []string in append
I've tried some things but didn't suceed, hope someone can point me what I'm doing wrong. Thanks in advance!
clearly the way you are appending in your loop will not result in the kinda list you want.
postArray = append(postArray, p...) // you are appending a vector here which you don't want in your case
instead use this,
postArray = append(postArray, p)

How to discard lines with certain strings in list and output to new list

I have a list downloaded from a website in XML. I am trying to filter the list by discarding lines that contain a certain string and building the same type of list without the lines containing the string.
I have a struct type that's having another struct.
I'm trying to use regexp and replaceall, and failing at replaceall.
func (*Regexp) ReplaceAll
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
There may be an entirely simpler way to filter a list to a new list that I'm missing somewhere, but I've found this as the closest possible solution so far. Please share other ways to grep and delete lines to a new list. The list is a byte at body and downloaded as a xml.
type PeopleList struct {
Peoples []Person `xml:"peoples>person"`
}
type Person struct {
ADD string `xml:"add,attr"`
Loc string `xml:"loc,attr"`
Har string `xml:"har,attr"`
Name string `xml:"name,attr"`
Country string `xml:"country,attr"`
Num string `xml:"num,attr"`
ADD2 string `xml:"add2,attr"`
Distance float64
func fetchPeopleList(userinfo Userinfo) PeopleList {
var p byte
jam, err := http.Get(string(peoplelisturl))
iferror (err)
body, err := ioutil.ReadAll(jam.Body)
peeps := body
reg := regexp.MustCompile("(?m)[\r\n]+^.*BAD:.*$")
rep := reg.ReplaceAll(peeps, p) // Here fails probably because of my syntax. Error: cannot use p (variable of type byte) as []byte value in argument to re.ReplaceAll
fmt.Println(rep)
iferror (err)
defer jam.Body.Close()
Finally, I would like a new list in the same format as the first, only without the lines containing the string.
Your question says you want to "discard lines", but Replace/ReplaceAll, as their names suggest, are for replacing matched patterns. Your regex is also a simple substring match, so the obvious solution would seem to be reading the file line by line and - as your title says - discarding lines containing the substring.
func fetchPeopleList(userinfo Userinfo) PeopleList {
jam, err := http.Get(string(peoplelisturl))
iferror (err)
br := bufio.NewReader(jam.Body)
defer jam.Body.Close()
for {
line,err := br.ReadString('\n')
if !strings.Contains(line, "BAD:") {
fmt.Println(line) // or whatever you want to do with non-discarded lines
}
if err != nil {
break
}
}

Is there a way of cleaning up this Go code?

I am just beginning to learn Go, and have made a function which parses markdown files with a header, containing some metadata (the files are blog posts).
here is an example:
---
Some title goes here
19 September 2012
---
This is some content, read it.
I've written this function, which works, but I feel it's quite verbose and messy, I've had a look at the various strings packages, but I don't know enough about Go and it's best practices to know what I should be doing differently, if I could get some tips to clean this up, I would appreciate it. (also, I know that i shouldn't be neglecting that error).
type Post struct {
Title string
Date string
Body string
}
func loadPost(title string) *Post {
filename := title + ".md"
file, _ := ioutil.ReadFile("posts/" + filename)
fileString := string(file)
str := strings.Split(fileString, "---")
meta := strings.Split(str[1], "\n")
title = meta[1]
date := meta[2]
body := str[2]
return &Post{Title: title, Date: date, Body: body}
}
I think it's not bad. A couple of suggestions:
The hard-coded slash in "posts/" is platform-dependent. You can use path/filepath.Join to avoid that.
There is bytes.Split, so you don't need the string(file).
You can create the Post without repeating the fields: &Post{title, date, body}
Alternatively, you could find out where the body starts with LastIndex(s, "--") and use that to index the file contents accordingly. This avoids the allocation of using Split.
const sep = "--"
func loadPost(content string) *Post {
sepLength := len(sep)
i := strings.LastIndex(content, sep)
headers := content[sepLength:i]
body := content[i+sepLength+1:]
meta := strings.Split(headers, "\n")
return &Post{meta[1], meta[2], body}
}
I agree that it's not bad. I'll add a couple of other ideas.
As Thomas showed, you don't need the intermediate variables title date and body. Try though,
return &Post{
Title: meta[1],
Date: meta[2],
Body: body,
}
It's true that you can leave the field names out, but I sometimes like them to keep the code self-documenting. (I think go vet likes them too.)
I fuss over strings versus byte slices, but probably more than I should. Since you're reading the file in one gulp, you probably don't need to worry about this. Converting everything to one big string and then slicing up the string is a handy way of doing things, just remember that you're pinning the entire string in memory if you keep any part of it. If your files are large or you have lots of them and you only end up keeping, say, the meta for most of them, this might not be the way to go.
There's just one blog entry per file? If so, I think I'll propose a variant of Thomas's suggestion. Verify the first bytes are --- (or your file is corrupt), then use strings.Index(fileString[3:], "---"). Split is more appropriate when you have an unknown number of segments. In your case you're just looking for that single separator after the meta. Index will find it after searching the meta and be done, without searching through the whole body. (And anyway, what if the body contained the string "---"?)
Finally, some people would use regular expressions for this. I still haven't warmed up to regular expressions, but anyway, it's another approach.
Sonia has some great suggestions. Below is my take which accounts for problems you might encounter when parsing the header.
http://play.golang.org/p/w-XYyhPj9n
package main
import (
"fmt"
"strings"
)
const sep = "---"
type parseError struct {
msg string
}
func (e *parseError) Error() string {
return e.msg
}
func parse(s string) (header []string, content string, err error) {
if !strings.HasPrefix(s, sep) {
return header, content, &parseError{"content does not start with `---`!"}
}
arr := strings.SplitN(s, sep, 3)
if len(arr) < 3 {
return header, content, &parseError{"header was not terminated with `---`!"}
}
header = strings.Split(strings.TrimSpace(arr[1]), "\n")
content = strings.TrimSpace(arr[2])
return header, content, nil
}
func main() {
//
f := `---
Some title goes here
19 September 2012
---
This is some content, read it. --Anonymous`
header, content, err := parse(f)
if err != nil {
panic(err)
}
for i, val := range header {
fmt.Println(i, val)
}
fmt.Println("---")
fmt.Println(content)
//
f = `---
Some title goes here
19 September 2012
This is some content, read it.`
_, _, err = parse(f)
fmt.Println("Error:", err)
//
f = `
Some title goes here
19 September 2012
---
This is some content, read it.`
_, _, err = parse(f)
fmt.Println("Error:", err)
}

Resources