author

Cara Replace String dalam Template Go


Sebuah masalah kecil yang saya temukan ketika mengedit skrip Blogimport. Saya ingin memperbesar ukuran gambar thumbnail dengan cara me-replace string yang ada di URL.

Misalnya, dari URL ini:

https://2.bp.blogspot.com/-DEeRanrBa6s/WGWGwA2qW5I/AAAAAAAADg4/feGUc-g9rXc9B7hXpKr0ecG9UOMXU3_VQCK4B/s72-c/pemrograman%2Bjavascript%2B-%2Bpetanikode.png

Menjadi seperti ini:

https://2.bp.blogspot.com/-DEeRanrBa6s/WGWGwA2qW5I/AAAAAAAADg4/feGUc-g9rXc9B7hXpKr0ecG9UOMXU3_VQCK4B/s1600/pemrograman%2Bjavascript%2B-%2Bpetanikode.png

Nah, di sana saya ingin merubah s72-c agar menjadi s1600. Pada template Hugo, saya bisa melakukannya seperti ini:

{{ replace .Media.ThumbnailUrl 's72-c' 's1600' }}

Namun, Go belum memiliki fungsi replace seperti Hugo. Setelah bolak-balik Google dan Stack Overflow, saya akhirnya membuat sebuah pertanyaan baru di Stack Overflow dan mendapatkan jawabannya.

Pertama, kita harus membuat fungsi replace dulu dengan Go.

func replace(input, from,to string) string {
    return strings.Replace(input,from,to, -1)
}

Kemudian melakukan mapping terhadap fungsi tadi agar nanti bisa dikenali dalam template.

funcMap = template.FuncMap{
     "replace":  replace,
}

Setelah itu, barulah kita masukan fungsinya ketika eksekusi template.

template := template.New("").Funcs(funcMap)

Barulah kita dapat menggunakan fungsi replace pada template go.

{{ replace .Media.ThumbnailUrl 's72-c' 's1600' }}

Contoh kode lengkapnya dapat juga di lihat di sini.

package main

import (
    "encoding/xml"
    "flag"
    "fmt"
    "log"
    "io/ioutil"
    "os"
    "path/filepath"
    "strings"
    "text/template"
    "time"
    "unicode"
)



type Date time.Time

func (d Date) String() string {
    return time.Time(d).Format("2006-01-02T15:04:05Z")
}

func (d *Date) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) error {
    var v string
    dec.DecodeElement(&v, &start)
    t, err := time.Parse("2006-01-02T15:04:05.000-07:00", v)
    if err != nil {
        return err
    }
    *d = Date(t)
    return nil
}

type Draft bool

func (d *Draft) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) error {
    var v string
    dec.DecodeElement(&v, &start)
    switch v {
    case "yes":
        *d = true
        return nil
    case "no":
        *d = false
        return nil
    }
    return fmt.Errorf("Unknown value for draft boolean: %s", v)
}

type AuthorImage struct{
    Src string `xml:"src,attr"`
}

type Author struct {
    Name string `xml:"name"`
    Uri string `xml:"uri"`
    Image AuthorImage `xml:"image"`
}

type Export struct {
    XMLName xml.Name `xml:"feed"`
    Entries []Entry `xml:"entry"`
}


type Media struct {
    ThumbnailUrl string `xml:"url,attr"`
}

type Entry struct {
    ID string `xml:"id"`
    Published Date `xml:"published"`
    Updated Date `xml:"updated"`
    Draft Draft `xml:"control>draft"`
    Title string `xml:"title"`
    Content string `xml:"content"`
    Tags Tags `xml:"category"`
    Author Author `xml:"author"`
    Media Media `xml:"thumbnail"`
    Extra string
}

type Tag struct {
    Name string `xml:"term,attr"`
    Scheme string `xml:"scheme,attr"`
}

type Tags []Tag

func (t Tags) TomlString() string {
    names := []string{}
    for _, t := range t {
        if t.Scheme == "http://www.blogger.com/atom/ns#" {
            names = append(names, fmt.Sprintf("%q", t.Name))
        }
    }
    return strings.Join(names, ", ")
}

var templ = `+++
title = "{{ .Title }}"
date = {{ .Published }}
updated = {{ .Updated }}{{ with .Tags.TomlString }}
tags = [{{ . }}]{{ end }}{{ if .Draft }}
draft = true{{ end }}
blogimport = true {{ with .Extra }}
{{.}}{{ end }}
[author]
    name = "{{ .Author.Name }}"
    uri = "{{ .Author.Uri }}"
    image = "{{ .Author.Image.Src }}"
[image]
    src = "{{ resizeImage .Media.ThumbnailUrl }}"
    link = ""
    thumblink = "{{ .Media.ThumbnailUrl }}"
    alt = ""
    title = ""
    author = ""
    license = ""
    licenseLink = ""
+++
{{ .Content }}
`

var t = template.Must(template.New("").Funcs(funcMap).Parse(templ))

// maps the the function into template
var funcMap = template.FuncMap{
        "resizeImage": resizeImage,
}

// Resize image of thumbnail to larger size (scale to 1600)
func resizeImage(url string) string {
    return strings.Replace(url, "s72-c", "s1600", -1)
}

func main() {
    log.SetFlags(0)

    extra := flag.String("extra", "", "additional metadata to set in frontmatter")
    flag.Parse()

    args := flag.Args()

    if len(args) != 2 {
        log.Printf("Usage: %s [options] <xmlfile> <targetdir>", os.Args[0])
        log.Println("options:")
        flag.PrintDefaults()
        os.Exit(1)
    }

    dir := args[1]

    info, err := os.Stat(dir)
    if os.IsNotExist(err) {
        err = os.MkdirAll(dir, 0755)
    }
    if err != nil {
        log.Fatal(err)
    }

    if !info.IsDir(){
        log.Fatal("Second argument is not a directory.")
     }


    b, err := ioutil.ReadFile(args[0])
    if err != nil {
        log.Fatal(err)
    }

    exp := Export{}

    err = xml.Unmarshal(b, &exp)
    if err != nil {
        log.Fatal(err)
    }

    if len(exp.Entries) < 1 {
        log.Fatal("No blog entries found!")
    }

    count := 0
    drafts := 0
    for _, entry := range exp.Entries {
        isPost := false
        for _, tag := range entry.Tags {
            if tag.Name == "http://schemas.google.com/blogger/2008/kind#post" &&
                tag.Scheme == "http://schemas.google.com/g/2005#kind" {
                isPost = true
                break
            }
        }
        if !isPost {
            continue
        }
        if extra != nil {
            entry.Extra = *extra
        }
        if err := writeEntry(entry, dir); err != nil {
            log.Fatalf("Failed writing post %q to disk:\n%s", entry.Title, err)
        }
        if entry.Draft {
            drafts++
        } else {
            count++
        }
    }
    log.Printf("Wrote %d published posts to disk.", count)
    log.Printf("Wrote %d drafts to disk.", drafts)
}

var delim = []byte("+++\n")

func writeEntry(e Entry, dir string) error {
    filename := filepath.Join(dir, makePath(e.Title)+".md")
    f, err := os.OpenFile(filename, os.O_CREATE | os.O_TRUNC | os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer f.Close()

    return t.Execute(f, e)
}


// Take a string with any characters and replace it so the string could be used in a path.
// E.g. Social Media -> social-media
func makePath(s string) string {
    return unicodeSanitize(strings.ToLower(strings.Replace(strings.TrimSpace(s), " ", "-", -1)))
}

func unicodeSanitize(s string) string {
    source := []rune(s)
    target := make([]rune, 0, len(source))

    for _, r := range source {
        if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '.' || r == '_' || r == '-' {
            target = append(target, r)
        }
    }

    return string(target)
}