devcenter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (192) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +39 -0
  5. data/Rakefile +2 -0
  6. data/bin/devcenter +7 -0
  7. data/devcenter.gemspec +30 -0
  8. data/lib/devcenter.rb +6 -0
  9. data/lib/devcenter/cli.rb +45 -0
  10. data/lib/devcenter/coderay_extensions.rb +70 -0
  11. data/lib/devcenter/commands.rb +4 -0
  12. data/lib/devcenter/commands/base.rb +47 -0
  13. data/lib/devcenter/commands/open.rb +34 -0
  14. data/lib/devcenter/commands/preview.rb +28 -0
  15. data/lib/devcenter/commands/pull.rb +37 -0
  16. data/lib/devcenter/helpers.rb +41 -0
  17. data/lib/devcenter/layout.html +299 -0
  18. data/lib/devcenter/md_parser.rb +87 -0
  19. data/lib/devcenter/previewer.rb +36 -0
  20. data/lib/devcenter/previewer/assets/images/public/article-icon-large.png +0 -0
  21. data/lib/devcenter/previewer/assets/images/public/article-icon.png +0 -0
  22. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_default.png +0 -0
  23. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_open.png +0 -0
  24. data/lib/devcenter/previewer/assets/images/public/body_bg.png +0 -0
  25. data/lib/devcenter/previewer/assets/images/public/callout_bg.png +0 -0
  26. data/lib/devcenter/previewer/assets/images/public/feed-icon-sprite.png +0 -0
  27. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo-mobile.png +0 -0
  28. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo.png +0 -0
  29. data/lib/devcenter/previewer/assets/images/public/heroku-logo.png +0 -0
  30. data/lib/devcenter/previewer/assets/images/public/icon_sprite_16.png +0 -0
  31. data/lib/devcenter/previewer/assets/images/public/index_li_bullet.png +0 -0
  32. data/lib/devcenter/previewer/assets/images/public/intro_bg.png +0 -0
  33. data/lib/devcenter/previewer/assets/images/public/jive_discussion_arrow.png +0 -0
  34. data/lib/devcenter/previewer/assets/images/public/jive_discussion_glyph.png +0 -0
  35. data/lib/devcenter/previewer/assets/images/public/line.png +0 -0
  36. data/lib/devcenter/previewer/assets/images/public/pre_code_background.png +0 -0
  37. data/lib/devcenter/previewer/assets/images/public/search-icon.png +0 -0
  38. data/lib/devcenter/previewer/assets/images/public/search_glyph.png +0 -0
  39. data/lib/devcenter/previewer/assets/images/public/search_return.png +0 -0
  40. data/lib/devcenter/previewer/assets/images/public/tag-icon-large.png +0 -0
  41. data/lib/devcenter/previewer/assets/images/public/toc-icon.png +0 -0
  42. data/lib/devcenter/previewer/assets/public.css +2125 -0
  43. data/lib/devcenter/previewer/assets/public/article-icon-large.png +0 -0
  44. data/lib/devcenter/previewer/assets/public/article-icon.png +0 -0
  45. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_default.png +0 -0
  46. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_open.png +0 -0
  47. data/lib/devcenter/previewer/assets/public/body_bg.png +0 -0
  48. data/lib/devcenter/previewer/assets/public/callout_bg.png +0 -0
  49. data/lib/devcenter/previewer/assets/public/feed-icon-sprite.png +0 -0
  50. data/lib/devcenter/previewer/assets/public/heroku-header-logo-mobile.png +0 -0
  51. data/lib/devcenter/previewer/assets/public/heroku-header-logo.png +0 -0
  52. data/lib/devcenter/previewer/assets/public/heroku-logo.png +0 -0
  53. data/lib/devcenter/previewer/assets/public/icon_sprite_16.png +0 -0
  54. data/lib/devcenter/previewer/assets/public/index_li_bullet.png +0 -0
  55. data/lib/devcenter/previewer/assets/public/intro_bg.png +0 -0
  56. data/lib/devcenter/previewer/assets/public/jive_discussion_arrow.png +0 -0
  57. data/lib/devcenter/previewer/assets/public/jive_discussion_glyph.png +0 -0
  58. data/lib/devcenter/previewer/assets/public/line.png +0 -0
  59. data/lib/devcenter/previewer/assets/public/pre_code_background.png +0 -0
  60. data/lib/devcenter/previewer/assets/public/public.css +2125 -0
  61. data/lib/devcenter/previewer/assets/public/search-icon.png +0 -0
  62. data/lib/devcenter/previewer/assets/public/search_glyph.png +0 -0
  63. data/lib/devcenter/previewer/assets/public/search_return.png +0 -0
  64. data/lib/devcenter/previewer/assets/public/tag-icon-large.png +0 -0
  65. data/lib/devcenter/previewer/assets/public/toc-icon.png +0 -0
  66. data/lib/devcenter/previewer/file_listener.rb +23 -0
  67. data/lib/devcenter/previewer/views/article.erb +345 -0
  68. data/lib/devcenter/previewer/web_app.rb +53 -0
  69. data/lib/devcenter/previewer/web_server.rb +29 -0
  70. data/lib/devcenter/version.rb +3 -0
  71. data/vendor/sinatra/.gitignore +6 -0
  72. data/vendor/sinatra/.travis.yml +16 -0
  73. data/vendor/sinatra/.yardopts +4 -0
  74. data/vendor/sinatra/AUTHORS +61 -0
  75. data/vendor/sinatra/Gemfile +91 -0
  76. data/vendor/sinatra/LICENSE +22 -0
  77. data/vendor/sinatra/README.de.rdoc +2116 -0
  78. data/vendor/sinatra/README.es.rdoc +2106 -0
  79. data/vendor/sinatra/README.fr.rdoc +2133 -0
  80. data/vendor/sinatra/README.hu.rdoc +608 -0
  81. data/vendor/sinatra/README.jp.rdoc +1056 -0
  82. data/vendor/sinatra/README.ko.rdoc +1932 -0
  83. data/vendor/sinatra/README.pt-br.rdoc +778 -0
  84. data/vendor/sinatra/README.pt-pt.rdoc +647 -0
  85. data/vendor/sinatra/README.rdoc +2049 -0
  86. data/vendor/sinatra/README.ru.rdoc +2033 -0
  87. data/vendor/sinatra/README.zh.rdoc +1816 -0
  88. data/vendor/sinatra/Rakefile +182 -0
  89. data/vendor/sinatra/examples/chat.rb +61 -0
  90. data/vendor/sinatra/examples/simple.rb +3 -0
  91. data/vendor/sinatra/examples/stream.ru +26 -0
  92. data/vendor/sinatra/lib/sinatra.rb +5 -0
  93. data/vendor/sinatra/lib/sinatra/base.rb +1820 -0
  94. data/vendor/sinatra/lib/sinatra/images/404.png +0 -0
  95. data/vendor/sinatra/lib/sinatra/images/500.png +0 -0
  96. data/vendor/sinatra/lib/sinatra/main.rb +30 -0
  97. data/vendor/sinatra/lib/sinatra/showexceptions.rb +345 -0
  98. data/vendor/sinatra/lib/sinatra/version.rb +3 -0
  99. data/vendor/sinatra/sinatra.gemspec +18 -0
  100. data/vendor/sinatra/test/base_test.rb +172 -0
  101. data/vendor/sinatra/test/builder_test.rb +91 -0
  102. data/vendor/sinatra/test/coffee_test.rb +90 -0
  103. data/vendor/sinatra/test/compile_test.rb +139 -0
  104. data/vendor/sinatra/test/contest.rb +98 -0
  105. data/vendor/sinatra/test/creole_test.rb +65 -0
  106. data/vendor/sinatra/test/delegator_test.rb +160 -0
  107. data/vendor/sinatra/test/encoding_test.rb +20 -0
  108. data/vendor/sinatra/test/erb_test.rb +98 -0
  109. data/vendor/sinatra/test/extensions_test.rb +98 -0
  110. data/vendor/sinatra/test/filter_test.rb +437 -0
  111. data/vendor/sinatra/test/haml_test.rb +91 -0
  112. data/vendor/sinatra/test/helper.rb +123 -0
  113. data/vendor/sinatra/test/helpers_test.rb +1768 -0
  114. data/vendor/sinatra/test/integration/app.rb +62 -0
  115. data/vendor/sinatra/test/integration_helper.rb +222 -0
  116. data/vendor/sinatra/test/integration_test.rb +87 -0
  117. data/vendor/sinatra/test/less_test.rb +69 -0
  118. data/vendor/sinatra/test/liquid_test.rb +59 -0
  119. data/vendor/sinatra/test/mapped_error_test.rb +305 -0
  120. data/vendor/sinatra/test/markaby_test.rb +80 -0
  121. data/vendor/sinatra/test/markdown_test.rb +82 -0
  122. data/vendor/sinatra/test/middleware_test.rb +68 -0
  123. data/vendor/sinatra/test/nokogiri_test.rb +67 -0
  124. data/vendor/sinatra/test/public/favicon.ico +0 -0
  125. data/vendor/sinatra/test/rabl_test.rb +89 -0
  126. data/vendor/sinatra/test/rack_test.rb +45 -0
  127. data/vendor/sinatra/test/radius_test.rb +59 -0
  128. data/vendor/sinatra/test/rdoc_test.rb +66 -0
  129. data/vendor/sinatra/test/readme_test.rb +120 -0
  130. data/vendor/sinatra/test/request_test.rb +45 -0
  131. data/vendor/sinatra/test/response_test.rb +64 -0
  132. data/vendor/sinatra/test/result_test.rb +76 -0
  133. data/vendor/sinatra/test/route_added_hook_test.rb +59 -0
  134. data/vendor/sinatra/test/routing_test.rb +1175 -0
  135. data/vendor/sinatra/test/sass_test.rb +116 -0
  136. data/vendor/sinatra/test/scss_test.rb +89 -0
  137. data/vendor/sinatra/test/server_test.rb +48 -0
  138. data/vendor/sinatra/test/settings_test.rb +561 -0
  139. data/vendor/sinatra/test/sinatra_test.rb +12 -0
  140. data/vendor/sinatra/test/slim_test.rb +84 -0
  141. data/vendor/sinatra/test/static_test.rb +219 -0
  142. data/vendor/sinatra/test/streaming_test.rb +149 -0
  143. data/vendor/sinatra/test/templates_test.rb +333 -0
  144. data/vendor/sinatra/test/textile_test.rb +65 -0
  145. data/vendor/sinatra/test/views/a/in_a.str +1 -0
  146. data/vendor/sinatra/test/views/ascii.erb +2 -0
  147. data/vendor/sinatra/test/views/b/in_b.str +1 -0
  148. data/vendor/sinatra/test/views/calc.html.erb +1 -0
  149. data/vendor/sinatra/test/views/error.builder +3 -0
  150. data/vendor/sinatra/test/views/error.erb +3 -0
  151. data/vendor/sinatra/test/views/error.haml +3 -0
  152. data/vendor/sinatra/test/views/error.sass +2 -0
  153. data/vendor/sinatra/test/views/explicitly_nested.str +1 -0
  154. data/vendor/sinatra/test/views/foo/hello.test +1 -0
  155. data/vendor/sinatra/test/views/hello.builder +1 -0
  156. data/vendor/sinatra/test/views/hello.coffee +1 -0
  157. data/vendor/sinatra/test/views/hello.creole +1 -0
  158. data/vendor/sinatra/test/views/hello.erb +1 -0
  159. data/vendor/sinatra/test/views/hello.haml +1 -0
  160. data/vendor/sinatra/test/views/hello.less +5 -0
  161. data/vendor/sinatra/test/views/hello.liquid +1 -0
  162. data/vendor/sinatra/test/views/hello.mab +1 -0
  163. data/vendor/sinatra/test/views/hello.md +1 -0
  164. data/vendor/sinatra/test/views/hello.nokogiri +1 -0
  165. data/vendor/sinatra/test/views/hello.rabl +2 -0
  166. data/vendor/sinatra/test/views/hello.radius +1 -0
  167. data/vendor/sinatra/test/views/hello.rdoc +1 -0
  168. data/vendor/sinatra/test/views/hello.sass +2 -0
  169. data/vendor/sinatra/test/views/hello.scss +3 -0
  170. data/vendor/sinatra/test/views/hello.slim +1 -0
  171. data/vendor/sinatra/test/views/hello.str +1 -0
  172. data/vendor/sinatra/test/views/hello.test +1 -0
  173. data/vendor/sinatra/test/views/hello.textile +1 -0
  174. data/vendor/sinatra/test/views/hello.wlang +1 -0
  175. data/vendor/sinatra/test/views/hello.yajl +1 -0
  176. data/vendor/sinatra/test/views/layout2.builder +3 -0
  177. data/vendor/sinatra/test/views/layout2.erb +2 -0
  178. data/vendor/sinatra/test/views/layout2.haml +2 -0
  179. data/vendor/sinatra/test/views/layout2.liquid +2 -0
  180. data/vendor/sinatra/test/views/layout2.mab +2 -0
  181. data/vendor/sinatra/test/views/layout2.nokogiri +3 -0
  182. data/vendor/sinatra/test/views/layout2.rabl +3 -0
  183. data/vendor/sinatra/test/views/layout2.radius +2 -0
  184. data/vendor/sinatra/test/views/layout2.slim +3 -0
  185. data/vendor/sinatra/test/views/layout2.str +2 -0
  186. data/vendor/sinatra/test/views/layout2.test +1 -0
  187. data/vendor/sinatra/test/views/layout2.wlang +2 -0
  188. data/vendor/sinatra/test/views/nested.str +1 -0
  189. data/vendor/sinatra/test/views/utf8.erb +2 -0
  190. data/vendor/sinatra/test/wlang_test.rb +70 -0
  191. data/vendor/sinatra/test/yajl_test.rb +86 -0
  192. metadata +414 -0
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in devcenter.gemspec
4
+ gemspec
5
+
6
+ # Temporary
7
+ # gem 'sinatra', :git => 'https://github.com/sinatra/sinatra.git', :ref => '9984d0d2b3f1ea12273c6c25f8e102b2329f33e9'
8
+
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Heroku
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,39 @@
1
+ # Devcenter
2
+
3
+ CLI to interact with Heroku's Dev Center
4
+
5
+ ## Installation
6
+
7
+ $ gem install devcenter
8
+
9
+ ## Usage
10
+
11
+ ### Open a published article
12
+
13
+ $ devcenter open error-pages
14
+
15
+ ### Save a local copy of an article
16
+
17
+ $ devcenter pull error-pages
18
+
19
+ This will save an `article-slug.md` text file in your local directory. The file includes some metadata (article title and id) followed by the article content in markdown format. You can edit both the title and the content, but **never overwrite the article id**.
20
+
21
+ ### Preview a local copy of an article
22
+
23
+ $ devcenter preview dynos
24
+
25
+ This will open a preview in your default browser and get it refreshed when you save the file. You can specify `--port` and `--host` options to customize the preview web server.
26
+
27
+ ### Help
28
+
29
+ Get available commands
30
+
31
+ $ devcenter help
32
+
33
+ Get help about a specific command
34
+
35
+ $ devcenter help pull
36
+
37
+ ## License
38
+
39
+ See LICENSE.txt file.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Trap interrupts to quit cleanly. See
4
+ # https://twitter.com/mitchellh/status/283014103189053442
5
+ Signal.trap("INT") { exit 1 }
6
+
7
+ require_relative '../lib/devcenter.rb'
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'devcenter/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "devcenter"
8
+ gem.version = Devcenter::VERSION
9
+ gem.authors = ["Raul Murciano"]
10
+ gem.email = ["raul@heroku.com"]
11
+ gem.description = %q{CLI to interact with Heroku's Dev Center}
12
+ gem.summary = %q{CLI to interact with Heroku's Dev Center}
13
+ gem.homepage = "https://devcenter.heroku.com"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = %w{ devcenter }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = %w{ lib vendor }
19
+ gem.add_runtime_dependency('commander', '~> 4.1.3')
20
+ gem.add_runtime_dependency('json', '~>1.7.6')
21
+ gem.add_runtime_dependency('excon', '~>0.15.4')
22
+ gem.add_runtime_dependency('launchy', '~>2.1.0')
23
+ gem.add_runtime_dependency('maruku', '~>0.6.1')
24
+ gem.add_runtime_dependency('nokogiri', '~>1.5.5')
25
+ gem.add_runtime_dependency('coderay', '~>1.0.8')
26
+ gem.add_runtime_dependency('thin', '~>1.5.0')
27
+ gem.add_runtime_dependency('rack', '~>1.4.4')
28
+ gem.add_runtime_dependency('rack-protection', '~>1.3.2')
29
+ gem.add_runtime_dependency('tilt', '~>1.3.3')
30
+ end
@@ -0,0 +1,6 @@
1
+ require_relative '../vendor/sinatra/lib/sinatra'
2
+ require_relative 'devcenter/version'
3
+ require_relative 'devcenter/helpers'
4
+ require_relative 'devcenter/previewer'
5
+ require_relative 'devcenter/coderay_extensions'
6
+ require_relative 'devcenter/cli'
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'commander/import'
5
+
6
+ program :version, Devcenter::VERSION
7
+ program :description, "CLI to interact with Heroku's Dev Center"
8
+
9
+ require_relative './commands'
10
+
11
+ command :open do |c|
12
+ c.syntax = 'devcenter open [options]'
13
+ c.summary = 'Open the article with the given slug in the default browser'
14
+ c.description = c.summary
15
+ c.example 'devcenter open process-model', 'Opens https://devcenter.heroku.com/articles/process-model in the default browser'
16
+ c.action do |args, options|
17
+ Devcenter::Commands::Open.run(args[0])
18
+ end
19
+ end
20
+
21
+ command :pull do |c|
22
+ c.syntax = 'devcenter pull [options]'
23
+ c.summary = 'Save an editable copy of an article in your current directory'
24
+ c.description = c.summary
25
+ c.example 'devcenter pull process-model', 'Saves the content of the article with the "process-model" slug to a local process-model.md file in the current directory'
26
+ c.option '--force', 'Skip confirmation and overwrite existing local file'
27
+ c.action do |args, options|
28
+ options.default :force => false
29
+ Devcenter::Commands::Pull.run(args[0], options.force)
30
+ end
31
+ end
32
+
33
+ command :preview do |c|
34
+ c.syntax = 'devcenter preview [options]'
35
+ c.summary = 'Opens a live preview for a given article file'
36
+ c.description = c.summary
37
+ c.example 'devcenter preview process-model', 'Opens a live preview of the local process-model.md file'
38
+
39
+ c.option '--host HOST', String, 'Host where the preview will be available'
40
+ c.option '--port PORT_NUMBER', Integer, 'Port where the preview will be available'
41
+ c.action do |args, options|
42
+ options.default :host => '127.0.0.1', :port => 3000
43
+ Devcenter::Commands::Preview.run(args[0], options.host, options.port)
44
+ end
45
+ end
@@ -0,0 +1,70 @@
1
+ module CodeRay
2
+
3
+ # Silent warnings
4
+ module Encoders
5
+ class Encoder
6
+ def << token
7
+ # unless @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN
8
+ # warn 'Using old Tokens#<< interface.'
9
+ # @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = true
10
+ # end
11
+ self.token(*token)
12
+ end
13
+ end
14
+ end
15
+
16
+ # Add terminal scanner
17
+ # author: Vincent Landgraf <setcool@gmx.de>
18
+ # modified by Raul Murciano <raul@murciano.net>
19
+ # licence: GPLv2.1
20
+ module Scanners
21
+
22
+ class Term < Scanner
23
+
24
+ register_for :term
25
+
26
+ def scan_tokens(tokens, options)
27
+ prev = nil
28
+
29
+ until eos?
30
+ line = scan(/.*?(\n|\z)/)
31
+ if line =~ /\A(\s*\$)/ # command
32
+ tokens << [$1, :comment] # prompt
33
+ line = line[($1.size)..-1]
34
+ if comment_index = line.index('#')
35
+ command = line[0..comment_index-1]
36
+ comment = line[comment_index-1..-1]
37
+ tokens << [command, :method] if command
38
+ tokens << [comment, :comment] if comment
39
+ else
40
+ tokens << [line, :method]
41
+ end
42
+ else
43
+ tokens << [line, :string]
44
+ end
45
+ prev = line
46
+ end
47
+ return tokens
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+ CodeRay::Scanners['java_script'].register_for :nodejs
55
+
56
+ module Rack
57
+ class Codehighlighter
58
+ def coderay(string)
59
+ lang = 'unknown'
60
+ refs = @opts[:pattern].match(string) # extract language name
61
+ if refs
62
+ lang = refs[1]
63
+ str = unescape_html(string.sub(@opts[:pattern], ""))
64
+ "<pre class='CodeRay' lang='#{lang}'>#{::CodeRay.encoder(:html).encode str, lang}</pre>"
65
+ else
66
+ "<pre class='CodeRay' lang='#{lang}'>#{string}</pre>"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'commands/base'
2
+ require_relative 'commands/open'
3
+ require_relative 'commands/pull'
4
+ require_relative 'commands/preview'
@@ -0,0 +1,47 @@
1
+ require 'excon'
2
+ require 'launchy'
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ module Devcenter::Commands
7
+
8
+ class Base
9
+
10
+ include Devcenter::Helpers
11
+
12
+ def self.run(*args)
13
+ command = self.new(*args)
14
+ end
15
+
16
+ def initialize(*args)
17
+ @validation_errors = []
18
+ validate
19
+ if @validation_errors.any?
20
+ @validation_errors.each{ |e| say e }
21
+ else
22
+ run
23
+ end
24
+ end
25
+
26
+ def validate; end # add error messages to @validation_errors
27
+ def run; end
28
+
29
+ protected
30
+
31
+ def article_not_found!(slug)
32
+ message = ["No #{slug} article found."]
33
+ suggestions = JSON.parse(Excon.get(search_api_url, :query => { :q => slug, :source => 'devcenter-cli' }).body)['devcenter']
34
+ suggestions.select!{ |s| article_url?(s['full_url']) }
35
+ suggestions.each{ |s| s['slug'] = slug_from_article_url(s['full_url']) }
36
+ unless suggestions.empty?
37
+ message << "Perhaps you meant one of these:"
38
+ longest = suggestions.map {|suggestion| suggestion['slug'].size }.max
39
+ suggestions.each do |suggestion|
40
+ message << " %-#{longest}s # %s" % [suggestion['slug'], suggestion['title']]
41
+ end
42
+ end
43
+ say message.join("\n")
44
+ exit
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ module Devcenter::Commands
2
+
3
+ class Open < Base
4
+
5
+ def initialize(*args)
6
+ @slug = args[0]
7
+ super
8
+ end
9
+
10
+ def validate
11
+ unless @slug && !@slug.strip.empty?
12
+ @validation_errors << 'Please provide a slug (e.g: ps is the slug for https://devcenter.heroku.com/articles/ps)'
13
+ end
14
+ end
15
+
16
+ def run
17
+ url = article_url(@slug)
18
+ head = Excon.head(url)
19
+ case head.status
20
+ when 200
21
+ say("Opening #{@slug} docs")
22
+ launchy = Launchy.open(url)
23
+ if launchy.respond_to?(:join)
24
+ launchy.join
25
+ end
26
+ when 301, 302
27
+ say "Redirected to #{head.headers['Location']}"
28
+ when 404
29
+ article_not_found!(@slug)
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ module Devcenter::Commands
2
+
3
+ class Preview < Base
4
+
5
+ def initialize(*args)
6
+ @slug = args[0].to_s.gsub(/.md\z/, '') # maybe the user provides the filename by mistake
7
+ @host = args[1]
8
+ @port = args[2]
9
+ @md_path = md_file_path(@slug)
10
+ super
11
+ end
12
+
13
+ def validate
14
+ empty_slug = @slug.nil? || @slug.to_s.strip.empty?
15
+ file_exists = !empty_slug && File.exists?(@md_path)
16
+ if empty_slug
17
+ @validation_errors << 'Please provide an article slug'
18
+ elsif !file_exists
19
+ @validation_errors << "Can't find #{@md_path} file - you may want to `devcenter pull #{@slug}`"
20
+ end
21
+ end
22
+
23
+ def run
24
+ ::Devcenter::Previewer.preview(@slug, @md_path, @host, @port)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ module Devcenter::Commands
2
+
3
+ class Pull < Base
4
+
5
+ def initialize(*args)
6
+ @slug = args[0]
7
+ @force_overwrite = args[1]
8
+ super
9
+ end
10
+
11
+ def validate
12
+ unless @slug && !@slug.strip.empty?
13
+ @validation_errors << 'Please provide a slug (e.g: ps is the slug for https://devcenter.heroku.com/articles/ps)'
14
+ end
15
+ end
16
+
17
+ def run
18
+ response = Excon.get(article_api_url(@slug))
19
+ article_received = response.status == 200 && JSON.parse(response.body)['article'] && JSON.parse(response.body)['article']['id']
20
+ article_not_found!(@slug) unless article_received
21
+
22
+ article = JSON.parse(response.body)['article']
23
+ metadata = {'title' => article['title'], 'id' => article['id']}
24
+ file_path = md_file_path(@slug)
25
+
26
+ unless @force_overwrite
27
+ cancel_save = File.exists?(file_path) && !agree("The file #{file_path} already exists - overwrite? (yes/no)")
28
+ return if cancel_save
29
+ end
30
+
31
+ file_content = [metadata.to_yaml, article['content']].join("\n\n")
32
+ write_file(file_path, file_content)
33
+ say "\"#{metadata['title']}\" article saved as #{file_path}"
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module Devcenter::Helpers
2
+
3
+ def devcenter_base_url
4
+ ENV['DEVCENTER_BASE_URL'] || 'https://devcenter.heroku.com'
5
+ end
6
+
7
+ def article_url(slug)
8
+ "#{devcenter_base_url}/articles/#{slug}"
9
+ end
10
+
11
+ def article_api_url(slug)
12
+ "#{article_url(slug)}.json"
13
+ end
14
+
15
+ def search_api_url
16
+ "#{devcenter_base_url}/articles.json"
17
+ end
18
+
19
+ def article_url?(url)
20
+ escaped_base_url = devcenter_base_url.gsub('/','\\/')
21
+ url.match(/\A#{escaped_base_url}\/articles\/.+/)
22
+ true
23
+ end
24
+
25
+ def slug_from_article_url(url)
26
+ url.split('/articles/').last
27
+ end
28
+
29
+ def md_file_path(slug)
30
+ File.expand_path("#{slug}.md")
31
+ end
32
+
33
+ def html_file_path(slug)
34
+ File.expand_path("#{slug}.html")
35
+ end
36
+
37
+ def write_file(filename, content)
38
+ File.open(filename, 'w'){ |f| f.write(content) }
39
+ end
40
+
41
+ end