Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Writefreely import #2

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions instances.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
; each instance gets a [section]
; each instance has a url and a token
[writeas]
url=https://write.as
token=00000000-0000-0000-0000-000000000000
324 changes: 280 additions & 44 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,75 +11,148 @@
package main

import (
"bufio"
"bytes"
"fmt"
"github.com/frankbille/go-wxr-import"
"github.com/writeas/godown"
"github.com/writeas/nerds/store"
"go.code.as/writeas.v2"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
)

var (
commentReg = regexp.MustCompile("(?m)<!-- ([^m ]|m[^o ]|mo[^r ]|mor[^e ])+ -->\n?")
)

func main() {
if len(os.Args) < 2 {
//errQuit("usage: wp-import https://write.as filename.xml")
errQuit("usage: wp-import filename.xml")
// Print the usage spec to the terminal and exit cleanly
func printUsage(help bool) {
usage := "usage: wp-import [-h|--help] [-i instance] [-f] filename.xml"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a bonus of using the flag package (as mentioned in another comment), we'll get all this output for free. So we probably won't need this func either.

if help {
usage = usage + "\n" +
" -h|--help Prints this help message.\n" +
" -i Specifies the instance to use.\n" +
" Should be one of the instances set up in instances.ini.\n" +
" Defaults to \"writeas\" (https://write.as).\n" +
" -f Specifies the filename to read from.\n" +
" This can be a relative or absolute path.\n" +
" The flag can be excluded if the filename is the last argument."
}
//instance := os.Args[1]
instance := "https://write.as"
fname := os.Args[1]
fmt.Println(usage)
os.Exit(0)
}

// TODO: load user config from same func as writeas-cli
t := ""
if t == "" {
errQuit("not authenticated. run: writeas auth <username>")
// This should allow input in these formats:
// wp-import -h (or --help)
// wp-import filename
// wp-import -i instance filename
// wp-import -i instance -f filename

func parseArgs(args []string) map[string]string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get rid of this func and make parsing less error-prone by using the built-in flag package. Here's a good example of how that can be done: https://github.com/writeas/wf-migrate/blob/master/cmd/wfimport/main.go

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After seeing your use of flag there I was planning on switching to it. :)

arguments := make(map[string]string)
if len(args) == 2 {
if args[1] == "-h" || args[1] == "--help" {
printUsage(true)
} else if string(args[1][0]) != "-" {
arguments["filename"] = args[1]
} else {
printUsage(false)
}
} else if len(args) < 2 {
printUsage(false)
} else {
// Starting at 1 because args[0] is the program name
for i := 1; i < len(args); i++ {
if args[i] == "-h" || args[i] == "--help" {
printUsage(true)
} else if args[i] == "-i" {
if i+1 == len(args) || string(args[i+1][0]) == "-" {
printUsage(false)
}
arguments["instance"] = args[i+1]
i++
} else if args[i] == "-f" {
if i+1 == len(args) || string(args[i+1][0]) == "-" {
printUsage(false)
}
arguments["filename"] = args[i+1]
i++
} else if i == len(args)-1 && string(args[i][0]) != "-" {
arguments["filename"] = args[i]
}
}
}
if arguments["filename"] == "" {
printUsage(false)
}
return arguments
}

cl := writeas.NewClientWith(writeas.Config{
URL: instance + "/api",
Token: t,
})
type instance struct {
Name string
Url string
Token string
}

log.Printf("Reading %s...\n", fname)
raw, _ := ioutil.ReadFile(fname)
type ImportedBlogs struct {
Collections []*SingleBlog
}

log.Println("Parsing...")
d := wxr.ParseWxr(raw)
log.Printf("Found %d channels.\n", len(d.Channels))
type SingleBlog struct {
Params *writeas.CollectionParams
Posts []*writeas.PostParams
}

postsCount := 0
// Preparing to be able to handle multiple types of files.
// For right now, just verify that it's a valid WordPress WXR file.
// Do this two ways: check that the file extension is "xml", and
// verify that the word "WordPress" appears in the first 200 characters.
func identifyFile(fname string, raw []byte) *ImportedBlogs {
parts := strings.Split(fname, ".")
extension := parts[len(parts)-1]
rawstr := string(raw[:200])

for _, ch := range d.Channels {
log.Printf("Channel: %s\n", ch.Title)
if extension == "xml" && strings.Contains(rawstr, "WordPress") {
log.Println("This looks like a WordPress file. Parsing...")
return parseWPFile(wxr.ParseWxr(raw))
} else {
errQuit("I can't tell what kind of file this is.")
}
// punt
return &ImportedBlogs{}
}

// Turn our WXR struct into an ImportedBlogs struct.
// Creating a general format for imported blogs is the first step to
// generalizing the import process.
func parseWPFile(d wxr.Wxr) *ImportedBlogs {
coll := &ImportedBlogs{}
for _, ch := range d.Channels {
// Create the blog
c := &writeas.CollectionParams{
Title: ch.Title,
Description: ch.Description,
c := &SingleBlog{
Params: &writeas.CollectionParams{
Title: ch.Title,
Description: ch.Description,
},
Posts: make([]*writeas.PostParams, 0, 0),
}
log.Printf("Creating %s...\n", ch.Title)
coll, err := cl.CreateCollection(c)
if err != nil {
errQuit(err.Error())
}
log.Printf("Done!\n")

log.Printf("Found %d items.\n", len(ch.Items))
for i, wpp := range ch.Items {
for _, wpp := range ch.Items {
if wpp.PostType != "post" {
continue
}

// Convert to Markdown
b := bytes.NewBufferString("")
r := bytes.NewReader([]byte(wpp.Content))
err = godown.Convert(b, r, nil)
err := godown.Convert(b, r, nil)
if err != nil {
errQuit(err.Error())
}
con := b.String()

// Remove unneeded WordPress comments that take up space, like <!-- wp:paragraph -->
Expand All @@ -98,18 +171,181 @@ func main() {
if tags != "" {
con += "\n\n" + tags
}

var postlang string
if len(ch.Language) > 2 {
postlang = string(ch.Language[:2])
} else {
postlang = ch.Language
}
p := &writeas.PostParams{
Title: wpp.Title,
Slug: wpp.PostName,
Content: con,
Created: &wpp.PostDateGmt,
Updated: &wpp.PostDateGmt,
Font: "norm",
Language: &ch.Language,
Collection: coll.Alias,
Title: wpp.Title,
Slug: wpp.PostName,
Content: con,
Created: &wpp.PostDateGmt,
Updated: &wpp.PostDateGmt,
Font: "norm",
Language: &postlang,
}
c.Posts = append(c.Posts, p)
}
coll.Collections = append(coll.Collections, c)
}
return coll
}

// Temporarily using an ini file to store instance tokens.
// This is probably not what the rest of the code does,
// but I need some way to handle this for now.
// TODO: Get this in line with the rest of the code (see T586)

// ini file format:
// Each instance has its own [section]
// Semicolons (;) at the beginning of a line indicate a comment
// Can't start a comment mid-line (this allows semicolons in variable values)
// Blank lines are ignored
func importConfig() map[string]instance {
file, err := ioutil.ReadFile("instances.ini")
if err != nil {
errQuit("Error reading instances.ini")
}
lines := strings.Split(string(file), "\n")
instances := make(map[string]instance)
curinst := ""
newinst := instance{}
for i := 0; i < len(lines); i++ {
line := lines[i]
if line == "" {
continue
}
fc := string(line[0])
if fc == ";" {
continue
}
if fc == "[" {
if curinst != "" {
instances[curinst] = newinst
newinst = instance{}
}
curinst = line[1:(len(line) - 1)]
} else {
loc := strings.Index(line, "=")
if curinst == "" || loc == -1 {
errQuit("Malformed ini file")
}
k := line[:loc]
v := line[loc+1:]
if k == "url" {
newinst.Url = v
} else if k == "token" {
newinst.Token = v
} else {
errQuit("Malformed ini file")
}
}
}
instances[curinst] = newinst
return instances
}

func main() {
a := parseArgs(os.Args)
// if len(os.Args) < 2 {
This conversation was marked as resolved.
Show resolved Hide resolved
// //errQuit("usage: wp-import https://write.as filename.xml")
// errQuit("usage: wp-import filename.xml")
// }
// fname := os.Args[1]
fname := a["filename"]
inst := "writeas"
if a["instance"] != "" {
inst = a["instance"]
}

instances := importConfig()
//fmt.Println(instances)
This conversation was marked as resolved.
Show resolved Hide resolved
var cl *writeas.Client
t := ""
u := ""
if val, ok := instances[inst]; ok {
t = val.Token
u = val.Url
cl = writeas.NewClientWith(writeas.Config{
URL: u + "/api",
Token: t,
})
} else {
fmt.Println("We don't have a token for " + inst + ".")
r := bufio.NewScanner(os.Stdin)
fmt.Print("Instance URL: ")
r.Scan()
url := r.Text()
if string(url[:5]) != "https" {
This conversation was marked as resolved.
Show resolved Hide resolved
url = "https://" + url
}
if string(url[len(url)-1:]) == "/" {
url = string(url[:len(url)-1])
}
//fmt.Println("Using URL", url)
This conversation was marked as resolved.
Show resolved Hide resolved
fmt.Print("Username: ")
r.Scan()
uname := r.Text()
//fmt.Println("Using username", uname)
fmt.Print("Password: ")
r.Scan()
passwd := r.Text()
//fmt.Println("Using password", passwd)
cl = writeas.NewClientWith(writeas.Config{
URL: url + "/api",
Token: "",
})
_, uerr := cl.LogIn(uname, passwd)
if uerr != nil {
errQuit("Couldn't log in with those credentials.")
}
file, _ := os.OpenFile("instances.ini", os.O_APPEND|os.O_WRONLY, 0644)
defer file.Close()
printstr := "\n[" + inst + "]\nurl=" + url + "\ntoken=" + cl.Token()
fmt.Fprintln(file, printstr)
fmt.Println("Okay, you're logged in.")
}

log.Printf("Reading %s...\n", fname)
raw, _ := ioutil.ReadFile(fname)

log.Println("Parsing...")

// What kind of file is it?
d := identifyFile(fname, raw) // d is now an ImportedBlogs object, not a WXR object

log.Printf("Found %d channels.\n", len(d.Collections))

postsCount := 0

for _, ch := range d.Collections {
c := ch.Params
title := c.Title
log.Printf("Channel: %s\n", title)

log.Printf("Creating %s...\n", title)
coll, err := cl.CreateCollection(c)
if err != nil {
if err.Error() == "Collection name is already taken." {
title = title + " " + store.GenerateFriendlyRandomString(4)
log.Printf("A blog by that name already exists. Changing to %s...\n", title)
c.Title = title
coll, err = cl.CreateCollection(c)
if err != nil {
errQuit(err.Error())
}
} else {
errQuit(err.Error())
}
}
log.Printf("Done!\n")

log.Printf("Found %d posts.\n", len(ch.Posts))
for _, p := range ch.Posts {
log.Printf("Creating %s", p.Title)
p.Collection = coll.Alias
_, err = cl.CreatePost(p)
if err != nil {
fmt.Fprintf(os.Stderr, "create post: %s\n", err)
Expand Down