-
Notifications
You must be signed in to change notification settings - Fork 4
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
base: master
Are you sure you want to change the base?
Changes from 12 commits
3dff306
6afec21
da6d104
80a3bb4
7140a21
76952b3
013ac35
c0964ce
9a80659
6cfeb65
e198fdc
acccce9
0de1a8e
93baa01
7be4a8d
cc85bab
ff443c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After seeing your use of |
||
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 --> | ||
|
@@ -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) | ||
|
There was a problem hiding this comment.
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.