zenpush 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +14 -8
- data/History.md +19 -0
- data/README.markdown +20 -9
- data/bin/zenpush +1 -1
- data/bin/zp +1 -1
- data/lib/zenpush/flavors/github.rb +41 -0
- data/lib/zenpush/flavors/standard.rb +13 -0
- data/lib/zenpush/markdown.rb +22 -6
- data/lib/zenpush/runner.rb +19 -18
- data/lib/zenpush/version.rb +1 -1
- data/lib/zenpush/zendesk.rb +73 -30
- data/lib/zenpush.rb +3 -3
- data/zenpush.gemspec +3 -2
- metadata +21 -3
- data/History.txt +0 -13
data/Gemfile.lock
CHANGED
@@ -1,28 +1,34 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
zenpush (0.
|
4
|
+
zenpush (0.2.0)
|
5
5
|
awesome_print (~> 1.0.0)
|
6
6
|
boson (~> 1.0)
|
7
7
|
httparty (~> 0.8.0)
|
8
8
|
json_pure (~> 1.5.1)
|
9
|
+
pygments.rb (~> 0.4.2)
|
9
10
|
redcarpet (~> 2.1.0)
|
10
11
|
|
11
12
|
GEM
|
12
13
|
remote: http://rubygems.org/
|
13
14
|
specs:
|
14
15
|
awesome_print (1.0.2)
|
15
|
-
boson (1.
|
16
|
-
httparty (0.8.
|
17
|
-
multi_json
|
16
|
+
boson (1.2.4)
|
17
|
+
httparty (0.8.3)
|
18
|
+
multi_json (~> 1.0)
|
18
19
|
multi_xml
|
19
|
-
json_pure (1.5.
|
20
|
+
json_pure (1.5.5)
|
20
21
|
spruz (~> 0.2.8)
|
21
|
-
multi_json (1.
|
22
|
-
multi_xml (0.
|
22
|
+
multi_json (1.6.1)
|
23
|
+
multi_xml (0.5.3)
|
24
|
+
posix-spawn (0.3.6)
|
25
|
+
pygments.rb (0.4.2)
|
26
|
+
posix-spawn (~> 0.3.6)
|
27
|
+
yajl-ruby (~> 1.1.0)
|
23
28
|
rake (0.9.2.2)
|
24
|
-
redcarpet (2.1.
|
29
|
+
redcarpet (2.1.1)
|
25
30
|
spruz (0.2.13)
|
31
|
+
yajl-ruby (1.1.0)
|
26
32
|
|
27
33
|
PLATFORMS
|
28
34
|
ruby
|
data/History.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
## 0.3.0
|
2
|
+
|
3
|
+
* [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown) support, with the `-F github` option. ([alexkwolfe](https://github.com/alexkwolfe))
|
4
|
+
* [Zendesk API v2](http://www.zendesk.com/blog/zendesk-api). `entries` become `topics`. ([alexkwolfe](https://github.com/alexkwolfe))
|
5
|
+
* Automatically create category and forum if either does not exist. ([alexkwolfe](https://github.com/alexkwolfe))
|
6
|
+
|
7
|
+
## 0.2.0
|
8
|
+
|
9
|
+
* HTML support: don't convert topics to HTML if they're not `.md` or `.markdown` files. ([nfo](https://github.com/nfo))
|
10
|
+
|
11
|
+
## 0.1.1
|
12
|
+
|
13
|
+
* New option `:filenames_use_dashes_instead_of_spaces`. ([torandu](https://github.com/torandu))
|
14
|
+
|
15
|
+
## 0.1.0
|
16
|
+
|
17
|
+
* Original version. ([nfo](https://github.com/nfo))
|
18
|
+
* List categories, forums, entries. ([nfo](https://github.com/nfo))
|
19
|
+
* Convert entries from Markdown to HTML before pushing them to Zendesk. ([nfo](https://github.com/nfo))
|
data/README.markdown
CHANGED
@@ -44,18 +44,18 @@ Additional configuration (optional):
|
|
44
44
|
|
45
45
|
$ zp forums
|
46
46
|
|
47
|
-
### Listing
|
47
|
+
### Listing topics in a forum
|
48
48
|
|
49
|
-
$ zp
|
49
|
+
$ zp topics -f <forum_id>
|
50
50
|
|
51
|
-
### Creating/updating
|
51
|
+
### Creating/updating a topic
|
52
52
|
|
53
|
-
Keep an organized folder of your categories, forums, and
|
53
|
+
Keep an organized folder of your categories, forums, and topics. Let's say I have the category "Documentation", containing a forum "REST API", and the topics "Introduction" and "Authentication"; you'll want to keep this file structure:
|
54
54
|
|
55
55
|
Documentation/REST API/Introduction.md
|
56
56
|
Documentation/REST API/Authentication.md
|
57
57
|
|
58
|
-
Creating or updating
|
58
|
+
Creating or updating a topic:
|
59
59
|
|
60
60
|
$ zp push -f <path_to_markdown_file>
|
61
61
|
$ zp push -f <path_to_html_file>
|
@@ -66,13 +66,24 @@ Following the previous example, you would type:
|
|
66
66
|
[~/KB/Documentation]$ zp push -f REST API/Authentication.md
|
67
67
|
[~/KB]$ zp push -f REST Documentation/API/Authentication.md
|
68
68
|
|
69
|
-
The gem will automatically discover the category and forum name of a given
|
69
|
+
The gem will automatically discover the category and forum name of a given topic file. It will also convert your Markdown syntax in HTML before sending it to Zendesk.
|
70
70
|
|
71
|
-
### Check if
|
71
|
+
### Check if a topic exists
|
72
72
|
|
73
73
|
$ zp exists? -f <path_to_markdown_file>
|
74
74
|
|
75
|
+
### Markdown Flavors
|
76
|
+
|
77
|
+
ZenPush supports two different flavors of markdown: 'standard' and 'github'. The latter flavor supports Github specific
|
78
|
+
syntax such as fenced code blocks and language specific highlighting.
|
79
|
+
|
80
|
+
Language highlighting alters the HTML inside code blocks to support styling. You will still need to specify the
|
81
|
+
appropriate CSS in your Zendesk account. Highlighting uses Pygments and any of the Pygments styles can be applied.
|
82
|
+
|
83
|
+
$ zp push -f <path_to_markdown_file> -F github
|
84
|
+
|
75
85
|
## Contributors
|
76
86
|
|
77
|
-
*
|
78
|
-
*
|
87
|
+
* [nfo](https://github.com/nfo)
|
88
|
+
* [torandu](https://github.com/torandu)
|
89
|
+
* [alexkwolfe](https://github.com/alexkwolfe)
|
data/bin/zenpush
CHANGED
data/bin/zp
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'redcarpet'
|
3
|
+
require 'pygments'
|
4
|
+
|
5
|
+
module ZenPush
|
6
|
+
module Flavors
|
7
|
+
|
8
|
+
class SyntaxRenderer < Redcarpet::Render::HTML
|
9
|
+
def block_code(code, language)
|
10
|
+
if language && !language.empty?
|
11
|
+
Pygments.highlight(code, :options => {:lexer => language.to_sym, :encoding => 'utf-8'})
|
12
|
+
else
|
13
|
+
"<pre>#{code}</pre>"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Github
|
19
|
+
def self.to_html(text)
|
20
|
+
renderer = SyntaxRenderer.new(optionize [
|
21
|
+
:with_toc_data,
|
22
|
+
:hard_wrap,
|
23
|
+
:xhtml
|
24
|
+
])
|
25
|
+
markdown = Redcarpet::Markdown.new(renderer, optionize([
|
26
|
+
:fenced_code_blocks,
|
27
|
+
:no_intra_emphasis,
|
28
|
+
:tables,
|
29
|
+
:autolink,
|
30
|
+
:strikethrough,
|
31
|
+
:space_after_headers
|
32
|
+
]))
|
33
|
+
markdown.render(text)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.optionize(options)
|
37
|
+
options.each_with_object({}) { |option, memo| memo[option] = true }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/zenpush/markdown.rb
CHANGED
@@ -1,13 +1,29 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
require 'redcarpet/compat'
|
3
2
|
|
4
3
|
module ZenPush
|
4
|
+
module Flavors
|
5
|
+
FILES = Dir[File.join(File.dirname(__FILE__), 'flavors', '**', '*.rb')].freeze
|
6
|
+
NAMES = FILES.collect { |f| File.basename(f, '.rb') }.freeze
|
7
|
+
FILES.each do |file|
|
8
|
+
require file
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
5
12
|
class Markdown
|
13
|
+
def self.to_zendesk_html(file, flavor=nil)
|
14
|
+
select_flavor(flavor).to_html(File.read(file))
|
15
|
+
end
|
6
16
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
::
|
17
|
+
def self.select_flavor(flavor)
|
18
|
+
flavor ||= :standard
|
19
|
+
flavor_class_name = flavor.to_s.capitalize.to_sym
|
20
|
+
if ZenPush::Flavors.const_defined?(flavor_class_name)
|
21
|
+
ZenPush::Flavors.const_get(flavor_class_name)
|
22
|
+
else
|
23
|
+
raise "The '#{flavor}' flavor is not supported. Use: #{ZenPush::Flavors::NAMES.join(', ')}."
|
24
|
+
end
|
11
25
|
end
|
12
26
|
end
|
13
|
-
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
data/lib/zenpush/runner.rb
CHANGED
@@ -16,40 +16,41 @@ module ZenPush
|
|
16
16
|
end
|
17
17
|
|
18
18
|
option :forum_id, :type => :numeric
|
19
|
-
desc 'List
|
20
|
-
def
|
21
|
-
ap ZenPush.z.
|
19
|
+
desc 'List topics'
|
20
|
+
def topics(options = {})
|
21
|
+
ap ZenPush.z.topics(options[:forum_id])
|
22
22
|
end
|
23
23
|
|
24
|
-
desc 'Does the
|
24
|
+
desc 'Does the topic matching the given file exist?'
|
25
25
|
option :file, :type => :string
|
26
26
|
def exists?(options = {})
|
27
|
-
category_name, forum_name,
|
28
|
-
|
29
|
-
ap !!
|
27
|
+
category_name, forum_name, topic_title = ZenPush.file_to_category_forum_topic(options[:file])
|
28
|
+
topic = ZenPush.z.find_topic(category_name, forum_name, topic_title)
|
29
|
+
ap !!topic
|
30
30
|
end
|
31
31
|
|
32
|
-
desc 'Create or update
|
32
|
+
desc 'Create or update a topic from the given file'
|
33
33
|
option :file, :type => :string
|
34
|
+
option :flavor, :type => :string
|
34
35
|
def push(options = {})
|
35
|
-
category_name, forum_name,
|
36
|
+
category_name, forum_name, topic_title = ZenPush.file_to_category_forum_topic(options[:file])
|
36
37
|
|
37
|
-
|
38
|
+
topic_body =
|
38
39
|
if options[:file].end_with?('.md') || options[:file].end_with?('.markdown')
|
39
|
-
ZenPush::Markdown.to_zendesk_html(options[:file])
|
40
|
+
ZenPush::Markdown.to_zendesk_html(options[:file], options[:flavor])
|
40
41
|
else
|
41
42
|
File.read(options[:file])
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
-
if
|
46
|
-
# UPDATE THE
|
47
|
-
ap ZenPush.z.
|
45
|
+
topic = ZenPush.z.find_topic(category_name, forum_name, topic_title)
|
46
|
+
if topic
|
47
|
+
# UPDATE THE TOPIC
|
48
|
+
ap ZenPush.z.put_topic(topic['id'], topic_body)
|
48
49
|
else
|
49
|
-
forum = ZenPush.z.
|
50
|
+
forum = ZenPush.z.find_or_create_forum(category_name, forum_name)
|
50
51
|
if forum
|
51
|
-
# CREATE THE
|
52
|
-
ap ZenPush.z.
|
52
|
+
# CREATE THE TOPIC
|
53
|
+
ap ZenPush.z.post_topic(forum['id'], topic_title, topic_body)
|
53
54
|
else
|
54
55
|
ap "Could not find a forum named '#{forum_name}' in the category '#{category_name}'"
|
55
56
|
exit(-1)
|
data/lib/zenpush/version.rb
CHANGED
data/lib/zenpush/zendesk.rb
CHANGED
@@ -33,7 +33,7 @@ module ZenPush
|
|
33
33
|
|
34
34
|
@options = opts
|
35
35
|
|
36
|
-
self.class.base_uri opts[:uri] + '/api/
|
36
|
+
self.class.base_uri opts[:uri] + '/api/v2'
|
37
37
|
self.class.basic_auth opts[:user], opts[:password]
|
38
38
|
end
|
39
39
|
|
@@ -54,7 +54,7 @@ module ZenPush
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def categories(options = {})
|
57
|
-
self.get('/categories.json', options).parsed_response
|
57
|
+
self.get('/categories.json', options).parsed_response['categories']
|
58
58
|
end
|
59
59
|
|
60
60
|
def category(category_id, options = {})
|
@@ -62,7 +62,7 @@ module ZenPush
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def forums(options = {})
|
65
|
-
self.get('/forums.json', options).parsed_response
|
65
|
+
self.get('/forums.json', options).parsed_response['forums']
|
66
66
|
end
|
67
67
|
|
68
68
|
def forum(forum_id, options = {})
|
@@ -70,20 +70,32 @@ module ZenPush
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def users(options = {})
|
73
|
-
self.get('/users.json', options).parsed_response
|
73
|
+
self.get('/users.json', options).parsed_response['users']
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
self.get("/forums/#{forum_id}/
|
76
|
+
def topics(forum_id, options = { })
|
77
|
+
self.get("/forums/#{forum_id}/topics.json", options).parsed_response['topics']
|
78
78
|
end
|
79
79
|
|
80
|
-
def
|
81
|
-
self.get("/
|
80
|
+
def topic(topic_id, options = {})
|
81
|
+
self.get("/topics/#{topic_id}.json", options).parsed_response
|
82
82
|
end
|
83
83
|
|
84
84
|
# Find category by name
|
85
85
|
def find_category(category_name, options = {})
|
86
|
-
self.categories
|
86
|
+
categories = self.categories
|
87
|
+
if categories.is_a?(Array)
|
88
|
+
categories.detect { |c| c['name'] == category_name }
|
89
|
+
else
|
90
|
+
raise "Could not retrieve categories: #{categories}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Find category by name, creating it if it doesn't exist
|
95
|
+
def find_or_create_category(category_name, options={ })
|
96
|
+
find_category(category_name, options) || begin
|
97
|
+
post_category(category_name, options={ })
|
98
|
+
end
|
87
99
|
end
|
88
100
|
|
89
101
|
# Find forum by name, knowing the category name
|
@@ -94,32 +106,63 @@ module ZenPush
|
|
94
106
|
end
|
95
107
|
end
|
96
108
|
|
97
|
-
#
|
98
|
-
def
|
109
|
+
# Given a category name, find a forum by name. Create the category and forum either doesn't exist.
|
110
|
+
def find_or_create_forum(category_name, forum_name, options={ })
|
111
|
+
category = self.find_or_create_category(category_name, options)
|
112
|
+
if category
|
113
|
+
self.forums.detect { |f| f['name'] == forum_name } || post_forum(category['id'], forum_name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Find topic by name, knowing the forum name and category name
|
118
|
+
def find_topic(category_name, forum_name, topic_title, options = {})
|
99
119
|
forum = self.find_forum(category_name, forum_name, options)
|
100
120
|
if forum
|
101
|
-
self.
|
121
|
+
self.topics(forum['id'], options).detect {|t| t['title'] == topic_title}
|
102
122
|
end
|
103
123
|
end
|
104
124
|
|
105
|
-
# Create
|
106
|
-
def
|
107
|
-
self.post(
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
)
|
114
|
-
end
|
115
|
-
|
116
|
-
#
|
117
|
-
def
|
118
|
-
self.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
125
|
+
# Create a category with a given name
|
126
|
+
def post_category(category_name, options={ })
|
127
|
+
self.post('/categories.json',
|
128
|
+
options.merge(
|
129
|
+
:body => { :category => {
|
130
|
+
:name => category_name
|
131
|
+
} }.to_json
|
132
|
+
)
|
133
|
+
)['category']
|
134
|
+
end
|
135
|
+
|
136
|
+
# Create a forum in the given category id
|
137
|
+
def post_forum(category_id, forum_name, options={ })
|
138
|
+
self.post('/forums.json',
|
139
|
+
options.merge(
|
140
|
+
:body => { :forum => {
|
141
|
+
:name => forum_name,
|
142
|
+
:category_id => category_id
|
143
|
+
} }.to_json
|
144
|
+
)
|
145
|
+
)['forum']
|
146
|
+
end
|
147
|
+
|
148
|
+
# Create a topic in the given forum id
|
149
|
+
def post_topic(forum_id, title, body, options = { })
|
150
|
+
self.post("/topics.json",
|
151
|
+
options.merge(
|
152
|
+
:body => { :topic => {
|
153
|
+
:forum_id => forum_id, :title => title, :body => body
|
154
|
+
} }.to_json
|
155
|
+
)
|
156
|
+
)['topic']
|
157
|
+
end
|
158
|
+
|
159
|
+
# Update a topic in the given forum id
|
160
|
+
def put_topic(id, body, options = { })
|
161
|
+
self.put("/topics/#{id}.json",
|
162
|
+
options.merge(
|
163
|
+
:body => { :topic => { :body => body } }.to_json
|
164
|
+
)
|
165
|
+
)['topic']
|
123
166
|
end
|
124
167
|
|
125
168
|
end
|
data/lib/zenpush.rb
CHANGED
@@ -11,7 +11,7 @@ module ZenPush
|
|
11
11
|
@z ||= ZenPush::Zendesk.new
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def file_to_category_forum_topic(file)
|
15
15
|
absolute_path = File.realpath(file)
|
16
16
|
file_extension = File.extname(file)
|
17
17
|
|
@@ -21,10 +21,10 @@ module ZenPush
|
|
21
21
|
parts.each { |el| el.gsub!(/-/, ' ') }
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
topic_name = File.basename(parts[-1], file_extension)
|
25
25
|
forum_name = parts[-2]
|
26
26
|
category_name = parts[-3]
|
27
27
|
|
28
|
-
return category_name, forum_name,
|
28
|
+
return category_name, forum_name, topic_name
|
29
29
|
end
|
30
30
|
end
|
data/zenpush.gemspec
CHANGED
@@ -17,12 +17,13 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
s.rdoc_options = ["--charset=UTF-8"]
|
20
|
-
|
20
|
+
|
21
21
|
s.add_dependency "boson", "~> 1.0" # Command line
|
22
22
|
s.add_dependency "httparty", "~> 0.8.0" # Zendesk API calls
|
23
23
|
s.add_dependency "redcarpet", "~> 2.1.0" # Markdown to HTML
|
24
|
+
s.add_dependency "pygments.rb", "~> 0.4.2" # Code highlighting for Github flavored markdown
|
24
25
|
s.add_dependency "awesome_print", "~> 1.0.0" # Colorized output of Zendesk responses
|
25
26
|
s.add_dependency "json_pure", "~> 1.5.1" # The C-gem "json" will still be used instead if it's installed
|
26
27
|
|
27
28
|
s.add_development_dependency "rake"
|
28
|
-
end
|
29
|
+
end
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: zenpush
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.3.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Nicolas Fouché
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -59,6 +59,22 @@ dependencies:
|
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: 2.1.0
|
61
61
|
none: false
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.4.2
|
68
|
+
none: false
|
69
|
+
name: pygments.rb
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.4.2
|
77
|
+
none: false
|
62
78
|
- !ruby/object:Gem::Dependency
|
63
79
|
version_requirements: !ruby/object:Gem::Requirement
|
64
80
|
requirements:
|
@@ -119,13 +135,15 @@ files:
|
|
119
135
|
- .gitignore
|
120
136
|
- Gemfile
|
121
137
|
- Gemfile.lock
|
122
|
-
- History.
|
138
|
+
- History.md
|
123
139
|
- LICENSE
|
124
140
|
- README.markdown
|
125
141
|
- Rakefile
|
126
142
|
- bin/zenpush
|
127
143
|
- bin/zp
|
128
144
|
- lib/zenpush.rb
|
145
|
+
- lib/zenpush/flavors/github.rb
|
146
|
+
- lib/zenpush/flavors/standard.rb
|
129
147
|
- lib/zenpush/markdown.rb
|
130
148
|
- lib/zenpush/runner.rb
|
131
149
|
- lib/zenpush/version.rb
|
data/History.txt
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
== 0.2.0
|
2
|
-
|
3
|
-
* HTML support: don't convert file before pushing it to Zendesk if it's not a .md or .markdown file.
|
4
|
-
|
5
|
-
== 0.1.1
|
6
|
-
|
7
|
-
* New option `:filenames_use_dashes_instead_of_spaces` (@torandu)
|
8
|
-
|
9
|
-
== 0.1.0
|
10
|
-
|
11
|
-
* Original version
|
12
|
-
* List categories, forums, entries
|
13
|
-
* Convert entries from Markdown to HTML push them to Zendesk
|