zenpush 0.2.0 → 0.3.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.
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