quickpress 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +45 -24
- data/bin/qp +433 -83
- data/lib/quickpress.rb +222 -13
- data/lib/quickpress/cli.rb +11 -2
- data/lib/quickpress/options.rb +20 -5
- data/lib/quickpress/version.rb +1 -1
- data/lib/quickpress/wordpress.rb +144 -4
- metadata +2 -2
data/lib/quickpress.rb
CHANGED
@@ -6,6 +6,7 @@ require 'quickpress/version'
|
|
6
6
|
require 'quickpress/wordpress'
|
7
7
|
require 'quickpress/cli'
|
8
8
|
require 'quickpress/options'
|
9
|
+
require 'digest/md5'
|
9
10
|
|
10
11
|
class String
|
11
12
|
# Removes starting whitespace.
|
@@ -36,7 +37,8 @@ module Quickpress
|
|
36
37
|
ROOT_DIR = File.expand_path "~/.config/quickpress"
|
37
38
|
CONFIG_FILE = "#{ROOT_DIR}/config.yml"
|
38
39
|
|
39
|
-
@@inited = nil
|
40
|
+
@@inited = nil # config
|
41
|
+
@@started = false # overall
|
40
42
|
@@ran_first_time = false
|
41
43
|
|
42
44
|
# URL of the default site used to post.
|
@@ -205,6 +207,7 @@ module Quickpress
|
|
205
207
|
|
206
208
|
File.write(CONFIG_FILE, YAML.dump(settings))
|
207
209
|
end
|
210
|
+
puts "Site added"
|
208
211
|
|
209
212
|
rescue StandardError => e
|
210
213
|
retry if e.message =~ /will retry/
|
@@ -303,6 +306,7 @@ module Quickpress
|
|
303
306
|
end
|
304
307
|
|
305
308
|
File.write(CONFIG_FILE, YAML.dump(settings))
|
309
|
+
puts "Forgotten"
|
306
310
|
|
307
311
|
# Ooh, boy
|
308
312
|
# We've just ran out of sites! Better delete that config file!
|
@@ -422,6 +426,38 @@ module Quickpress
|
|
422
426
|
end
|
423
427
|
end
|
424
428
|
|
429
|
+
# Pretty-prints categories list.
|
430
|
+
def list_categories
|
431
|
+
Quickpress::startup
|
432
|
+
|
433
|
+
# Will show categories in columns of n
|
434
|
+
columns = 5
|
435
|
+
table = @@connection.categories.each_slice(columns).to_a
|
436
|
+
|
437
|
+
puts
|
438
|
+
Thor::Shell::Basic.new.print_table table
|
439
|
+
end
|
440
|
+
|
441
|
+
# Pretty-prints all options of the Wordpress site.
|
442
|
+
def list_options
|
443
|
+
Quickpress::startup
|
444
|
+
options = @@connection.get_options
|
445
|
+
|
446
|
+
puts
|
447
|
+
Thor::Shell::Basic.new.print_table options
|
448
|
+
end
|
449
|
+
|
450
|
+
# Pretty-prints all users currently registered on the site.
|
451
|
+
def list_users
|
452
|
+
Quickpress::startup
|
453
|
+
users = @@connection.get_users
|
454
|
+
|
455
|
+
users.each do |user|
|
456
|
+
puts
|
457
|
+
Thor::Shell::Basic.new.print_table user
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
425
461
|
# Actually sends post/page `filename` to the blog.
|
426
462
|
def new_file(what, filename)
|
427
463
|
Quickpress::startup
|
@@ -431,16 +467,24 @@ module Quickpress
|
|
431
467
|
id, link = nil, nil
|
432
468
|
|
433
469
|
if what == :post
|
434
|
-
title = CLI::get "Post title:"
|
435
470
|
|
436
|
-
|
437
|
-
|
471
|
+
# User specified title/categories on command line?
|
472
|
+
title = $options[:title]
|
473
|
+
if title.nil?
|
474
|
+
title = CLI::get "Post title:"
|
475
|
+
end
|
438
476
|
|
439
|
-
|
440
|
-
|
441
|
-
|
477
|
+
categories = $options[:category]
|
478
|
+
if categories.nil?
|
479
|
+
puts "Existing blog categories:"
|
480
|
+
Quickpress::list_categories
|
481
|
+
puts
|
482
|
+
puts "Use a comma-separated list (eg. 'cat1, cat2, cat3')"
|
483
|
+
puts "Tab-completion works."
|
484
|
+
puts "(will create non-existing categories automatically)"
|
442
485
|
|
443
|
-
|
486
|
+
categories = CLI::tab_complete("Post categories:", @@connection.categories)
|
487
|
+
end
|
444
488
|
|
445
489
|
cats = []
|
446
490
|
categories.split(',').each { |c| cats << c.lstrip.strip }
|
@@ -458,7 +502,10 @@ module Quickpress
|
|
458
502
|
puts "Post successful!"
|
459
503
|
|
460
504
|
elsif what == :page
|
461
|
-
title =
|
505
|
+
title = $options[:title]
|
506
|
+
if title.nil?
|
507
|
+
title = CLI::get "Page title:"
|
508
|
+
end
|
462
509
|
|
463
510
|
CLI::with_status("Creating page...") do
|
464
511
|
|
@@ -477,6 +524,131 @@ module Quickpress
|
|
477
524
|
END
|
478
525
|
end
|
479
526
|
|
527
|
+
# Entrance for when we're editing a page or a post
|
528
|
+
# with numerical `id` (`what` says so).
|
529
|
+
#
|
530
|
+
def edit(what, id, filename=nil)
|
531
|
+
Quickpress::startup
|
532
|
+
|
533
|
+
# Get previous content
|
534
|
+
old_content = nil
|
535
|
+
if what == :post
|
536
|
+
post = @@connection.get_post id
|
537
|
+
|
538
|
+
old_content = post["post_content"]
|
539
|
+
else
|
540
|
+
page = @@connection.get_page id
|
541
|
+
|
542
|
+
old_content = page["post_content"]
|
543
|
+
end
|
544
|
+
|
545
|
+
if filename.nil?
|
546
|
+
|
547
|
+
# Get editor to open temporary file
|
548
|
+
editor = ENV["EDITOR"]
|
549
|
+
if editor.nil?
|
550
|
+
editor = get("Which text editor we'll use?")
|
551
|
+
end
|
552
|
+
|
553
|
+
# Create draft file
|
554
|
+
tempfile = Tempfile.new ['quickpress', '.html']
|
555
|
+
tempfile.write old_content
|
556
|
+
tempfile.flush
|
557
|
+
|
558
|
+
# Oh yeah, baby
|
559
|
+
`#{editor} #{tempfile.path}`
|
560
|
+
|
561
|
+
if tempfile.size.zero?
|
562
|
+
puts "Empty file: did nothing"
|
563
|
+
tempfile.close
|
564
|
+
exit 666
|
565
|
+
end
|
566
|
+
|
567
|
+
tempfile.close # Apparently, calling `tempfile.flush`
|
568
|
+
tempfile.open # won't do it. Need to close and reopen.
|
569
|
+
|
570
|
+
md5old = Digest::MD5.hexdigest old_content
|
571
|
+
md5new = Digest::MD5.hexdigest tempfile.read
|
572
|
+
|
573
|
+
if (md5old == md5new) and (not $options[:force])
|
574
|
+
puts "Contents unchanged: skipping"
|
575
|
+
puts "(use --force if you want to do it anyway)"
|
576
|
+
return
|
577
|
+
end
|
578
|
+
|
579
|
+
puts "File: '#{tempfile.path}'" if $options[:debug]
|
580
|
+
|
581
|
+
edit_file(what, id, tempfile.path)
|
582
|
+
tempfile.close
|
583
|
+
|
584
|
+
else
|
585
|
+
|
586
|
+
md5old = Digest::MD5.hexdigest old_content
|
587
|
+
md5new = Digest::MD5.hexdigest File.read(filename)
|
588
|
+
|
589
|
+
if (md5old == md5new) and (not $options[:force])
|
590
|
+
puts "Contents unchanged: skipping"
|
591
|
+
puts "(use --force if you want to do it anyway)"
|
592
|
+
return
|
593
|
+
end
|
594
|
+
|
595
|
+
edit_file(what, id, filename)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
# Actually edits post/page `filename` to the blog.
|
600
|
+
def edit_file(what, id, filename)
|
601
|
+
|
602
|
+
html = Tilt.new(filename).render
|
603
|
+
|
604
|
+
link = nil
|
605
|
+
|
606
|
+
if what == :post
|
607
|
+
|
608
|
+
# User specified title/categories on command line?
|
609
|
+
title = $options[:title]
|
610
|
+
if title.nil?
|
611
|
+
title = CLI::get("New Post title:", true)
|
612
|
+
end
|
613
|
+
|
614
|
+
categories = $options[:category]
|
615
|
+
if categories.nil?
|
616
|
+
puts "Existing blog categories:"
|
617
|
+
Quickpress::list_categories
|
618
|
+
puts
|
619
|
+
puts "Use a comma-separated list (eg. 'cat1, cat2, cat3')"
|
620
|
+
puts "Tab-completion works."
|
621
|
+
puts "(will create non-existing categories automatically)"
|
622
|
+
puts "(leave empty to keep current categories)"
|
623
|
+
|
624
|
+
categories = CLI::tab_complete("Post categories:", @@connection.categories)
|
625
|
+
end
|
626
|
+
|
627
|
+
cats = []
|
628
|
+
categories.split(',').each { |c| cats << c.lstrip.strip }
|
629
|
+
|
630
|
+
CLI::with_status("Editing post...") do
|
631
|
+
link = @@connection.edit_post(id, html, title, categories)
|
632
|
+
end
|
633
|
+
|
634
|
+
elsif what == :page
|
635
|
+
title = $options[:title]
|
636
|
+
if title.nil?
|
637
|
+
title = CLI::get("New Page title:", true)
|
638
|
+
end
|
639
|
+
|
640
|
+
CLI::with_status("Editing Page...") do
|
641
|
+
link = @@connection.edit_page(id, html, title)
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
puts <<-END.remove_starting!
|
646
|
+
Edit successful!
|
647
|
+
link: #{link}
|
648
|
+
END
|
649
|
+
end
|
650
|
+
|
651
|
+
|
480
652
|
# Deletes comma-separated list of posts/pages with `ids`.
|
481
653
|
# A single number is ok too.
|
482
654
|
def delete(what, ids)
|
@@ -527,6 +699,7 @@ module Quickpress
|
|
527
699
|
@@connection.delete_page id.to_i
|
528
700
|
end
|
529
701
|
end
|
702
|
+
puts "Deleted!"
|
530
703
|
end
|
531
704
|
end
|
532
705
|
|
@@ -570,12 +743,35 @@ module Quickpress
|
|
570
743
|
end
|
571
744
|
end
|
572
745
|
|
746
|
+
# Shows comment count according to their status.
|
747
|
+
def status_comments
|
748
|
+
Quickpress::startup
|
749
|
+
status = @@connection.get_comment_status
|
750
|
+
|
751
|
+
puts
|
752
|
+
Thor::Shell::Basic.new.print_table status
|
753
|
+
end
|
754
|
+
|
755
|
+
def status_categories
|
756
|
+
Quickpress::startup
|
757
|
+
status = @@connection.get_category_status
|
758
|
+
|
759
|
+
if $options[:"non-empty"]
|
760
|
+
status.reject! { |s| s[1].zero? }
|
761
|
+
end
|
762
|
+
|
763
|
+
puts
|
764
|
+
Thor::Shell::Basic.new.print_table status
|
765
|
+
end
|
766
|
+
|
573
767
|
private
|
574
768
|
module_function
|
575
769
|
|
576
770
|
# Initializes everything based on the config file or
|
577
771
|
# simply by asking the user.
|
578
772
|
def startup
|
773
|
+
return if @started
|
774
|
+
|
579
775
|
Quickpress::first_time if @@default_site.nil?
|
580
776
|
|
581
777
|
puts "Using site '#{@@default_site}'"
|
@@ -585,15 +781,28 @@ module Quickpress
|
|
585
781
|
CLI::with_status("Connecting...") do
|
586
782
|
@@connection ||= Wordpress.new(@@default_site, @@username, @@password)
|
587
783
|
end
|
784
|
+
@started = true
|
588
785
|
end
|
589
786
|
|
590
|
-
# Gets username and password.
|
787
|
+
# Gets username and password for the Wordpress site.
|
788
|
+
#
|
789
|
+
# There's three ways of authenticating,
|
790
|
+
# in order of importance:
|
591
791
|
#
|
592
|
-
#
|
593
|
-
# `
|
792
|
+
# 1. Command-line options.
|
793
|
+
# User can specify user/pass with the `-u` and `-p`
|
794
|
+
# options, overrides everything else.
|
795
|
+
# 2. Environment variables.
|
796
|
+
# Whatever's inside `QP_USERNAME` and `QP_PASSWORD`
|
797
|
+
# envvars will be used.
|
798
|
+
# 3. Ask the user.
|
799
|
+
# If all else fails, we'll ask the user for
|
800
|
+
# username/password.
|
594
801
|
#
|
595
|
-
# If that fails, asks to the user.
|
596
802
|
def authenticate
|
803
|
+
@@username ||= $options[:user]
|
804
|
+
@@password ||= $options[:pass]
|
805
|
+
|
597
806
|
@@username ||= ENV["QP_USERNAME"]
|
598
807
|
@@password ||= ENV["QP_PASSWORD"]
|
599
808
|
|
data/lib/quickpress/cli.rb
CHANGED
@@ -18,11 +18,20 @@ module Quickpress
|
|
18
18
|
#
|
19
19
|
# This ensures that the string is not empty
|
20
20
|
# and pre/post spaces are removed.
|
21
|
-
|
21
|
+
#
|
22
|
+
# If `allow_empty` is true, allows the user to
|
23
|
+
# type an empty input.
|
24
|
+
#
|
25
|
+
def get(prompt, allow_empty=false)
|
22
26
|
print "#{prompt} "
|
23
27
|
|
24
28
|
ret = ""
|
25
|
-
|
29
|
+
|
30
|
+
if allow_empty
|
31
|
+
ret = $stdin.gets.lstrip.strip
|
32
|
+
else
|
33
|
+
ret = $stdin.gets.lstrip.strip while ret.empty?
|
34
|
+
end
|
26
35
|
ret
|
27
36
|
end
|
28
37
|
|
data/lib/quickpress/options.rb
CHANGED
@@ -1,18 +1,33 @@
|
|
1
1
|
|
2
2
|
module Quickpress
|
3
|
+
|
4
|
+
# Hash that holds global program's settings.
|
5
|
+
#
|
6
|
+
# There should be only one instalce of it.
|
7
|
+
#
|
3
8
|
class Options
|
4
9
|
def initialize
|
5
|
-
@
|
6
|
-
@options[:force] = false
|
7
|
-
@options[:markup] = 'markdown'
|
10
|
+
@values = {}
|
8
11
|
end
|
9
12
|
|
10
13
|
def [] label
|
11
|
-
@
|
14
|
+
@values[label]
|
12
15
|
end
|
13
16
|
|
14
17
|
def []=(label, val)
|
15
|
-
@
|
18
|
+
@values[label] = val
|
19
|
+
end
|
20
|
+
|
21
|
+
# To add settings saved on other hash.
|
22
|
+
#
|
23
|
+
# @note I don't use Hash#merge because Thor's
|
24
|
+
# argument list creates a Hash with darn
|
25
|
+
# Strings as keys.
|
26
|
+
# I want symbols, dammit!
|
27
|
+
def merge! other_hash
|
28
|
+
other_hash.each do |key, val|
|
29
|
+
@values[key.to_sym] = val
|
30
|
+
end
|
16
31
|
end
|
17
32
|
end
|
18
33
|
|
data/lib/quickpress/version.rb
CHANGED
data/lib/quickpress/wordpress.rb
CHANGED
@@ -17,6 +17,9 @@ module Quickpress
|
|
17
17
|
# All categories on blog
|
18
18
|
attr_reader :categories
|
19
19
|
|
20
|
+
# Blog's options in a Hash. Need to call `get_options` first.
|
21
|
+
attr_reader :options
|
22
|
+
|
20
23
|
# Yes it is
|
21
24
|
VERY_LARGE_NUMBER = 2**31 - 1
|
22
25
|
|
@@ -44,15 +47,17 @@ module Quickpress
|
|
44
47
|
:username => user,
|
45
48
|
:password => pass)
|
46
49
|
|
47
|
-
# Actually connecting
|
48
|
-
options = @client.getOptions
|
50
|
+
# Actually connecting, takes a while
|
51
|
+
options = @client.getOptions(:options => ["blog_title",
|
52
|
+
"blog_tagline",
|
53
|
+
"blog_url"])
|
49
54
|
|
50
55
|
@title = options["blog_title"]["value"]
|
51
56
|
@tagline = options["blog_tagline"]["value"]
|
52
57
|
@url = options["blog_url"]["value"]
|
53
58
|
|
54
59
|
@categories = []
|
55
|
-
terms = @client.getTerms(:taxonomy =>
|
60
|
+
terms = @client.getTerms(:taxonomy => "category")
|
56
61
|
terms.each do |term|
|
57
62
|
@categories << term["name"]
|
58
63
|
end
|
@@ -61,15 +66,71 @@ module Quickpress
|
|
61
66
|
# Sends a post/page to the Wordpress site.
|
62
67
|
def post options
|
63
68
|
id = @client.newPost(:content => options)
|
64
|
-
|
69
|
+
|
70
|
+
info = @client.getPost(:post_id => id,
|
71
|
+
:fields => [:link])
|
72
|
+
link = info["link"]
|
65
73
|
|
66
74
|
return id, link
|
67
75
|
end
|
68
76
|
|
77
|
+
# Returns post with numerical `id`.
|
78
|
+
# It's a Hash with attributes/values.
|
79
|
+
#
|
69
80
|
def get_post id
|
70
81
|
@client.getPost(:post_id => id)
|
71
82
|
end
|
72
83
|
|
84
|
+
# Edits post with numerical `id` to have `new_content`.
|
85
|
+
#
|
86
|
+
# If any of the arguments is nil, will keep their
|
87
|
+
# old values.
|
88
|
+
#
|
89
|
+
def edit_post(id, new_content="", new_title="", new_categories=[])
|
90
|
+
old_post = get_post id
|
91
|
+
|
92
|
+
new_content = old_post["post_content"] if new_content.empty?
|
93
|
+
new_title = old_post["post_title"] if new_title.empty?
|
94
|
+
|
95
|
+
if new_categories == []
|
96
|
+
|
97
|
+
# Filtering out terms that are not categories
|
98
|
+
terms = old_post["terms"].select { |t| t["taxonomy"] == "category" }
|
99
|
+
|
100
|
+
# Getting category names
|
101
|
+
cats = terms.map { |c| c["name"] }
|
102
|
+
new_categories = cats
|
103
|
+
end
|
104
|
+
|
105
|
+
@client.editPost(:post_id => id,
|
106
|
+
:content => {
|
107
|
+
:post_content => new_content,
|
108
|
+
:post_title => new_title,
|
109
|
+
:terms_names => {
|
110
|
+
:category => new_categories
|
111
|
+
}
|
112
|
+
})
|
113
|
+
old_post["link"]
|
114
|
+
end
|
115
|
+
|
116
|
+
def edit_page(id, new_content="", new_title="")
|
117
|
+
old_page = get_page id
|
118
|
+
|
119
|
+
new_content = old_page["post_content"] if new_content.empty?
|
120
|
+
new_title = old_page["post_title"] if new_title.empty?
|
121
|
+
|
122
|
+
@client.editPost(:post_id => id,
|
123
|
+
:filter => {
|
124
|
+
:post_type => 'page'
|
125
|
+
},
|
126
|
+
:content => {
|
127
|
+
:post_content => new_content,
|
128
|
+
:post_title => new_title
|
129
|
+
})
|
130
|
+
old_page["link"]
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns page with numerical `id`.
|
73
134
|
def get_page id
|
74
135
|
@client.getPost(:post_id => id,
|
75
136
|
:filter => {
|
@@ -98,10 +159,12 @@ module Quickpress
|
|
98
159
|
})
|
99
160
|
end
|
100
161
|
|
162
|
+
# Deletes post with numerical `id`.
|
101
163
|
def delete_post id
|
102
164
|
@client.deletePost(:post_id => id)
|
103
165
|
end
|
104
166
|
|
167
|
+
# Deletes page with numerical `id`.
|
105
168
|
def delete_page id
|
106
169
|
@client.deletePost(:post_id => id,
|
107
170
|
:filter => {
|
@@ -109,6 +172,83 @@ module Quickpress
|
|
109
172
|
})
|
110
173
|
end
|
111
174
|
|
175
|
+
# Retrieves as much metadata about the blog as it can.
|
176
|
+
#
|
177
|
+
# Returns an array of 3-element arrays:
|
178
|
+
#
|
179
|
+
# 1. Wordpress' internal option name.
|
180
|
+
# You must use it to set options. See `set_options`.
|
181
|
+
# 2. Human-readable description of the option.
|
182
|
+
# 3. Current value.
|
183
|
+
#
|
184
|
+
# The values are detailed here:
|
185
|
+
# http://codex.wordpress.org/Option_Reference
|
186
|
+
#
|
187
|
+
def get_options
|
188
|
+
options = @client.getOptions
|
189
|
+
|
190
|
+
options.map { |o| [o[0], o[1]["desc"], o[1]["value"]] }
|
191
|
+
end
|
192
|
+
|
193
|
+
# Sets the blog's options according to `new_options`
|
194
|
+
# hash.
|
195
|
+
# It points to an array with two elements:
|
196
|
+
#
|
197
|
+
# 1. Wordpress' internal option name.
|
198
|
+
# See `get_options`.
|
199
|
+
# 2. It's new value.
|
200
|
+
# See link on `get_options` for possible values.
|
201
|
+
#
|
202
|
+
# Returns the new options, the same way as `get_options`.
|
203
|
+
#
|
204
|
+
def set_options new_options
|
205
|
+
options = @client.setOptions
|
206
|
+
|
207
|
+
options.map { |o| [o[0], o[1]["desc"], o[1]["value"]] }
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns all users currently registered on the blog.
|
212
|
+
#
|
213
|
+
# It's an Array of two-element Arrays:
|
214
|
+
#
|
215
|
+
# 1. Wordpress' internal info name
|
216
|
+
# 2. It's value
|
217
|
+
def get_users
|
218
|
+
users = @client.getUsers
|
219
|
+
|
220
|
+
# Replacing XML-RPC's ugly DateTime class
|
221
|
+
# with Ruby's Time
|
222
|
+
users.each do |u|
|
223
|
+
u["registered"] = u["registered"].to_time
|
224
|
+
end
|
225
|
+
|
226
|
+
users.map { |u| u.to_a }
|
227
|
+
end
|
228
|
+
|
229
|
+
# Returns comment counts according to their status.
|
230
|
+
# It's an Array of two elements:
|
231
|
+
#
|
232
|
+
# 1. Wordpress' internal status name
|
233
|
+
# 2. Comment counts on that status
|
234
|
+
#
|
235
|
+
def get_comment_status
|
236
|
+
status = @client.getCommentCount
|
237
|
+
|
238
|
+
status.to_a
|
239
|
+
end
|
240
|
+
|
241
|
+
# Returns categories and how many posts they have.
|
242
|
+
# It's an Array of two elements:
|
243
|
+
#
|
244
|
+
# 1. Category name
|
245
|
+
# 2. Post count
|
246
|
+
#
|
247
|
+
def get_category_status
|
248
|
+
status = @client.getTerms(:taxonomy => 'category')
|
249
|
+
|
250
|
+
status.map { |s| [s["name"], s["count"]] } # all we need
|
251
|
+
end
|
112
252
|
end
|
113
253
|
end
|
114
254
|
|