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 CHANGED
@@ -1,28 +1,34 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- zenpush (0.1.1)
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.1.0)
16
- httparty (0.8.1)
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.4)
20
+ json_pure (1.5.5)
20
21
  spruz (~> 0.2.8)
21
- multi_json (1.4.0)
22
- multi_xml (0.4.1)
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.0)
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 entries in a forum
47
+ ### Listing topics in a forum
48
48
 
49
- $ zp entries -f <forum_id>
49
+ $ zp topics -f <forum_id>
50
50
 
51
- ### Creating/updating an entry
51
+ ### Creating/updating a topic
52
52
 
53
- Keep an organized folder of your categories, forums, and entries. Let's say I have the category "Documentation", containing a forum "REST API", and the entries "Introduction" and "Authentication"; you'll want to keep this file structure:
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 an entry:
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 entry file. It will also convert your Markdown syntax in HTML before sending it to Zendesk.
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 an entry exists
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
- * @nfo
78
- * @torandu
87
+ * [nfo](https://github.com/nfo)
88
+ * [torandu](https://github.com/torandu)
89
+ * [alexkwolfe](https://github.com/alexkwolfe)
data/bin/zenpush CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'zenpush'
4
- ZenPush::Runner.start
4
+ ZenPush::Runner.start
data/bin/zp CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'zenpush'
4
- ZenPush::Runner.start
4
+ ZenPush::Runner.start
@@ -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
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+ require 'redcarpet/compat'
3
+
4
+ module ZenPush
5
+ module Flavors
6
+ module Standard
7
+ def self.to_html(content)
8
+ ::Markdown.new(content).to_html.gsub(/<\/?code>/, '')
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -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
- # Convert a markdown file to HTML, removing all <code> tags,
8
- # which make Zendesk remove carriage returns.
9
- def self.to_zendesk_html(file)
10
- ::Markdown.new(File.read(file)).to_html.gsub(/<\/?code>/, '')
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
+
@@ -16,40 +16,41 @@ module ZenPush
16
16
  end
17
17
 
18
18
  option :forum_id, :type => :numeric
19
- desc 'List entries'
20
- def entries(options = {})
21
- ap ZenPush.z.entries(options[:forum_id])
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 entry matching the given file exist ?'
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, entry_title = ZenPush.file_to_category_forum_entry(options[:file])
28
- entry = ZenPush.z.find_entry(category_name, forum_name, entry_title)
29
- ap !!entry
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 an entry from the given file'
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, entry_title = ZenPush.file_to_category_forum_entry(options[:file])
36
+ category_name, forum_name, topic_title = ZenPush.file_to_category_forum_topic(options[:file])
36
37
 
37
- entry_body =
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
- entry = ZenPush.z.find_entry(category_name, forum_name, entry_title)
45
- if entry
46
- # UPDATE THE ENTRY
47
- ap ZenPush.z.put_entry(entry['id'], entry_body)
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.find_forum(category_name, forum_name)
50
+ forum = ZenPush.z.find_or_create_forum(category_name, forum_name)
50
51
  if forum
51
- # CREATE THE ENTRY
52
- ap ZenPush.z.post_entry(forum['id'], entry_title, entry_body)
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)
@@ -1,4 +1,4 @@
1
1
  # encoding: UTF-8
2
2
  module ZenPush
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
@@ -33,7 +33,7 @@ module ZenPush
33
33
 
34
34
  @options = opts
35
35
 
36
- self.class.base_uri opts[:uri] + '/api/v1'
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 entries(forum_id, options = {})
77
- self.get("/forums/#{forum_id}/entries.json", options).parsed_response
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 entry(entry_id, options = {})
81
- self.get("/entries/#{entry_id}.json", options).parsed_response
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.detect {|c| c['name'] == category_name}
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
- # Find entry by name, knowing the forum name and category name
98
- def find_entry(category_name, forum_name, entry_title, options = {})
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.entries(forum['id'], options).detect {|e| e['title'] == entry_title}
121
+ self.topics(forum['id'], options).detect {|t| t['title'] == topic_title}
102
122
  end
103
123
  end
104
124
 
105
- # Create an entry in the given forum id
106
- def post_entry(forum_id, entry_title, entry_body, options = {})
107
- self.post("/entries.json",
108
- options.merge(
109
- :body => { :entry => {
110
- :forum_id => forum_id, :title => entry_title, :body => entry_body
111
- } }.to_json
112
- )
113
- )
114
- end
115
-
116
- # Update an entry in the given forum id
117
- def put_entry(entry_id, entry_body, options = {})
118
- self.put("/entries/#{entry_id}.json",
119
- options.merge(
120
- :body => { :entry => { :body => entry_body } }.to_json
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 file_to_category_forum_entry(file)
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
- entry_name = File.basename(parts[-1], file_extension)
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, entry_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.2.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-07 00:00:00.000000000 Z
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.txt
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