I have a very simple markdown app in go which works great but I am really struggling to sort the order of the index posts on the page and would like a neat way in the file to do this. Any help appreciated.
html is
<section>
{{range .}}
<h2 class="h2_home">{{.Title}} ({{.Date}})</h2>
<p>{{.Summary}}</p>
{{end}}
</section>
and the go stuff for the index page is as follows
func getPosts() []Post {
a := []Post{}
files, _ := filepath.Glob("posts/*")
for _, f := range files {
file := strings.Replace(f, "posts/", "", -1)
file = strings.Replace(file, ".md", "", -1)
fileread, _ := ioutil.ReadFile(f)
lines := strings.Split(string(fileread), "\n")
title := string(lines[0])
date := string(lines[1])
summary := string(lines[2])
body := strings.Join(lines[3:len(lines)], "\n")
htmlBody := template.HTML(blackfriday.MarkdownCommon([]byte(body)))
a = append(a, Post{title, date, summary, htmlBody, file, nil})
}
return a
}
Ive not looked at it for a while as it just works but I really want to put something into the file to support ordering. The .md file is formatted
Hello Go lang markdown blog generator!
12th Jan 2015
This is a basic start to my own hosted go lang markdown static blog/ web generator.
### Here I am...
This entry is a no whistles Hello ... etc
See sort.Slice in the sort package. Here is an example of usage.
For your particular problem, you have a slice a []Post, so just call sort.Slice on it like this:
sort.Slice(a, func(i, j int) bool { return a[i].title < a[j].title })
This is just sorting all members of the slice a based on their title (assuming you can access this private field of a Post in this package, if not you'd need to add a function).
If you do that your slice a of posts will be sorted by title (or any other criteria you wish, perhaps you could give the user a choice of title or date?). You don't need to adjust your markdown files.
If you want to sort on dates obviously your Post should parse the dates with the time package first so that you have real dates, not just a string.
So something like this (assuming time.Time on the Post):
// parse dates
date,err := time.Parse("2nd Jan 2006",string(lines[1]))
if err != nil {
// deal with it
}
// later, sort the slice
sort.Slice(a, func(i, j int) bool { return a[i].date.Before(a[j].date)})
Related
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 am creating an IRC bot using Go as a first project to get to grips with the language. One of the bot functions is to grab data from the TVmaze API and display in the channel.
I have imported an env package which allows the bot admin to define how the output is displayed.
For example SHOWSTRING="#showname# - #status# – #network.name#"
I am trying to add functionality to it so that the admin can use IRC formatting functionality which is accessed with \u0002 this is bold \u0002 for example.
I have a function which generates the string that is being returned and displayed in the channel.
func generateString(show Show) string {
str := os.Getenv("SHOWSTRING")
r := strings.NewReplacer(
"#ID#", string(show.ID),
"#showname#", show.Name,
"#status#", show.Status,
"#network.name#", show.Network.Name,
)
result := r.Replace(str)
return result
}
From what i have read i think that i need to use the rune datatype instead of string and then converting the runes into a string before being output.
I am using the https://github.com/thoj/go-irceven package for interacting with IRC.
Although i think that using rune is the correct way to go, i have tried a few things that have confused me.
If i add \u0002 to the SHOWSTRING from the env, it returns \u0002House\u0002 - Ended - Fox. I am doing this by con.Privmsg(roomName, tvmaze.ShowLookup('house'))
However if i try con.Privmsg(roomName, "\u0002This should be bold\u0002") it outputs bold text.
What is the best option here? If it is converting the string into runes and then back to a string, how do i go about doing that?
I needed to use strconv.Unquote() on my return in the function.
The new generateString function now outputs the correct string and looks like this
func generateString(show Show) string {
str := os.Getenv("SHOWSTRING")
r := strings.NewReplacer(
"#ID#", string(show.ID),
"#showname#", show.Name,
"#status#", show.Status,
"#network.name#", show.Network.Name,
)
result := r.Replace(str)
ret, err := strconv.Unquote(`"` + result + `"`)
if err != nil {
fmt.Println("Error unquoting the string")
}
return ret
}
I am new to Go and I am struggling trying to figure out a way to return unique variables from an array in Go templating language. This is to configure some software and I do not have access to the source to change the actual program only the template.
I have knocked up an example in the Go playground:
https://play.golang.org/
package main
import "os"
import "text/template"
func main() {
var arr [10]string
arr[0]="mice"
arr[1]="mice"
arr[2]="mice"
arr[3]="mice"
arr[4]="mice"
arr[5]="mice"
arr[6]="mice"
arr[7]="toad"
arr[8]="toad"
arr[9]="mice"
tmpl, err := template.New("test").Parse("{{range $index, $thing := $}}The thing is: {{$thing}}\n{{end}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, arr)
if err != nil { panic(err) }
}
Right now this returns:
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: toad
The thing is: toad
The thing is: mice
What I am trying to do is craft a template that from the input array filters duplicates and only returns:
The thing is: mice
The thing is: toad
I am really stuck as I know virtually no go and struggle to find any array manipulation methods in the docs. Any one have any tips?
Addenium
Sorry for not being clear I wrote this question on the bus on the way to work.
I don't have access to any go code outside the template. I have a template I can edit and within that template I have an array that may or may not have multiple values and I need to print them once.
I appreciate this is not how templates are meant to work but if there is some dirty way to do this it would save me several days work.
You can create your own functions for the template via template.FuncMap:
arr := []string{
"mice",
"mice",
"mice",
"mice",
"mice",
"mice",
"mice",
"toad",
"toad",
"mice",
}
customFunctions := template.FuncMap{"unique" : unique}
tmpl, err := template.New("test").Funcs(customFunctions).Parse("{{range $index, $thing := unique $}}The thing is: {{$thing}}\n{{end}}")
Where unique is defined as:
func unique(e []string) []string {
r := []string{}
for _, s := range e {
if !contains(r[:], s) {
r = append(r, s)
}
}
return r
}
func contains(e []string, c string) bool {
for _, s := range e {
if s == c {
return true
}
}
return false
}
Output:
The thing is: mice
The thing is: toad
(It might be better to use a map .. but this gives you the basic idea)
That said - have you considered filtering this outside of the template? That would make things nicer for you.. then you can just iterate over the actual slice within the template.
Working sample: https://play.golang.org/p/L_8t10CpHW
The template can acces a particular custom function http://golang.org/pkg/text/template/#FuncMap. This allows your own logic to be called from within the template.
There is a comprehensive example in the docs which I wont repeat here. The key line is setting up a funcion Map and providing this to the template:
tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText)
Then can be accessed within the template.
{{myCustomFuction .}}
Since now it is established that it will take more go code in the form of a mapped function to achieve, I thought I would share a thought that might get the job done. If you have control of which template that the 'go' program you are not able to modify runs, then you could make several passes. I am also assuming you are on linux. Something like this:
goexe 'first template' // this writes to 'text file'
cat textfile | sort | uniq > 'text file'
goexe 'second template' // your desired output
I have a map of values that looks like this:
vals := map[string]interface{}{"foo": 1, "bar": 2, "baz": 7}
data := map[string]interface{}{"bat": "obj", "values": vals}
What should my template look like to generate the following string (note the correct comma usage)?
SET obj.foo=1, obj.bar=2, obj.baz=7
I started with this as my template:
SET {{range $i, $v := .values}} {{.bat}}.{{$i}}={{$v}},{{end}}
But that just prints out
SET
And even if that did work, the commas would be incorrect. I then tried to use a custom function to format the map, but I couldn't get the template to ever call my function. None of the following seemed to work:
SET {{.MyFunction .values}}
SET {{call .MyFunction .values}}
SET {{call MyFunction .values}}
when MyFunction was defined as:
func MyFunction(data map[string]interface{}) string {
fmt.PrintLn('i was called!')
return "foo"
}
And I'm executing the templates using a helper function that looks like this:
func useTemplate(name string, data interface{}) string {
out := new(bytes.Buffer)
templates[name].Execute(out, data)
return string(out.Bytes())
}
Thanks!
This will get you pretty close:
SET {{range $key, $value := $.values}}{{$.bat}}.{{$key}}={{$value}} {{end}}
rendering as:
SET obj.bar=2 obj.baz=7 obj.foo=1
Unfortunately, I don't think there's any simple way to have the commas added in between the values due to how the range action iterates on maps (there's no numeric index). That said, the template packages were meant to be easily extensible so you can have less logic in your templates and more logic in Go itself, so it's easy enough to code a helper function in Go and make it available to your templates.
If you're happy to go that extra mile, then the template becomes much simpler, and also more efficient. The function can look like this:
func commaJoin(prefix string, m map[string]interface{}) string {
var buf bytes.Buffer
first := true
for k, v := range m {
if !first {
buf.WriteString(", ")
}
first = false
buf.WriteString(prefix)
buf.WriteByte('.')
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(fmt.Sprint(v))
}
return buf.String()
}
and your template would look like:
SET {{$.values | commaJoin $.bat}}
Here is a working example with this logic:
http://play.golang.org/p/5lFUpFCzZm
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)
}