devcenter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/Rakefile +2 -0
- data/bin/devcenter +7 -0
- data/devcenter.gemspec +30 -0
- data/lib/devcenter.rb +6 -0
- data/lib/devcenter/cli.rb +45 -0
- data/lib/devcenter/coderay_extensions.rb +70 -0
- data/lib/devcenter/commands.rb +4 -0
- data/lib/devcenter/commands/base.rb +47 -0
- data/lib/devcenter/commands/open.rb +34 -0
- data/lib/devcenter/commands/preview.rb +28 -0
- data/lib/devcenter/commands/pull.rb +37 -0
- data/lib/devcenter/helpers.rb +41 -0
- data/lib/devcenter/layout.html +299 -0
- data/lib/devcenter/md_parser.rb +87 -0
- data/lib/devcenter/previewer.rb +36 -0
- data/lib/devcenter/previewer/assets/images/public/article-icon-large.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/article-icon.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_default.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_open.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/body_bg.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/callout_bg.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/feed-icon-sprite.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/heroku-header-logo-mobile.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/heroku-header-logo.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/heroku-logo.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/icon_sprite_16.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/index_li_bullet.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/intro_bg.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/jive_discussion_arrow.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/jive_discussion_glyph.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/line.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/pre_code_background.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/search-icon.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/search_glyph.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/search_return.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/tag-icon-large.png +0 -0
- data/lib/devcenter/previewer/assets/images/public/toc-icon.png +0 -0
- data/lib/devcenter/previewer/assets/public.css +2125 -0
- data/lib/devcenter/previewer/assets/public/article-icon-large.png +0 -0
- data/lib/devcenter/previewer/assets/public/article-icon.png +0 -0
- data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_default.png +0 -0
- data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_open.png +0 -0
- data/lib/devcenter/previewer/assets/public/body_bg.png +0 -0
- data/lib/devcenter/previewer/assets/public/callout_bg.png +0 -0
- data/lib/devcenter/previewer/assets/public/feed-icon-sprite.png +0 -0
- data/lib/devcenter/previewer/assets/public/heroku-header-logo-mobile.png +0 -0
- data/lib/devcenter/previewer/assets/public/heroku-header-logo.png +0 -0
- data/lib/devcenter/previewer/assets/public/heroku-logo.png +0 -0
- data/lib/devcenter/previewer/assets/public/icon_sprite_16.png +0 -0
- data/lib/devcenter/previewer/assets/public/index_li_bullet.png +0 -0
- data/lib/devcenter/previewer/assets/public/intro_bg.png +0 -0
- data/lib/devcenter/previewer/assets/public/jive_discussion_arrow.png +0 -0
- data/lib/devcenter/previewer/assets/public/jive_discussion_glyph.png +0 -0
- data/lib/devcenter/previewer/assets/public/line.png +0 -0
- data/lib/devcenter/previewer/assets/public/pre_code_background.png +0 -0
- data/lib/devcenter/previewer/assets/public/public.css +2125 -0
- data/lib/devcenter/previewer/assets/public/search-icon.png +0 -0
- data/lib/devcenter/previewer/assets/public/search_glyph.png +0 -0
- data/lib/devcenter/previewer/assets/public/search_return.png +0 -0
- data/lib/devcenter/previewer/assets/public/tag-icon-large.png +0 -0
- data/lib/devcenter/previewer/assets/public/toc-icon.png +0 -0
- data/lib/devcenter/previewer/file_listener.rb +23 -0
- data/lib/devcenter/previewer/views/article.erb +345 -0
- data/lib/devcenter/previewer/web_app.rb +53 -0
- data/lib/devcenter/previewer/web_server.rb +29 -0
- data/lib/devcenter/version.rb +3 -0
- data/vendor/sinatra/.gitignore +6 -0
- data/vendor/sinatra/.travis.yml +16 -0
- data/vendor/sinatra/.yardopts +4 -0
- data/vendor/sinatra/AUTHORS +61 -0
- data/vendor/sinatra/Gemfile +91 -0
- data/vendor/sinatra/LICENSE +22 -0
- data/vendor/sinatra/README.de.rdoc +2116 -0
- data/vendor/sinatra/README.es.rdoc +2106 -0
- data/vendor/sinatra/README.fr.rdoc +2133 -0
- data/vendor/sinatra/README.hu.rdoc +608 -0
- data/vendor/sinatra/README.jp.rdoc +1056 -0
- data/vendor/sinatra/README.ko.rdoc +1932 -0
- data/vendor/sinatra/README.pt-br.rdoc +778 -0
- data/vendor/sinatra/README.pt-pt.rdoc +647 -0
- data/vendor/sinatra/README.rdoc +2049 -0
- data/vendor/sinatra/README.ru.rdoc +2033 -0
- data/vendor/sinatra/README.zh.rdoc +1816 -0
- data/vendor/sinatra/Rakefile +182 -0
- data/vendor/sinatra/examples/chat.rb +61 -0
- data/vendor/sinatra/examples/simple.rb +3 -0
- data/vendor/sinatra/examples/stream.ru +26 -0
- data/vendor/sinatra/lib/sinatra.rb +5 -0
- data/vendor/sinatra/lib/sinatra/base.rb +1820 -0
- data/vendor/sinatra/lib/sinatra/images/404.png +0 -0
- data/vendor/sinatra/lib/sinatra/images/500.png +0 -0
- data/vendor/sinatra/lib/sinatra/main.rb +30 -0
- data/vendor/sinatra/lib/sinatra/showexceptions.rb +345 -0
- data/vendor/sinatra/lib/sinatra/version.rb +3 -0
- data/vendor/sinatra/sinatra.gemspec +18 -0
- data/vendor/sinatra/test/base_test.rb +172 -0
- data/vendor/sinatra/test/builder_test.rb +91 -0
- data/vendor/sinatra/test/coffee_test.rb +90 -0
- data/vendor/sinatra/test/compile_test.rb +139 -0
- data/vendor/sinatra/test/contest.rb +98 -0
- data/vendor/sinatra/test/creole_test.rb +65 -0
- data/vendor/sinatra/test/delegator_test.rb +160 -0
- data/vendor/sinatra/test/encoding_test.rb +20 -0
- data/vendor/sinatra/test/erb_test.rb +98 -0
- data/vendor/sinatra/test/extensions_test.rb +98 -0
- data/vendor/sinatra/test/filter_test.rb +437 -0
- data/vendor/sinatra/test/haml_test.rb +91 -0
- data/vendor/sinatra/test/helper.rb +123 -0
- data/vendor/sinatra/test/helpers_test.rb +1768 -0
- data/vendor/sinatra/test/integration/app.rb +62 -0
- data/vendor/sinatra/test/integration_helper.rb +222 -0
- data/vendor/sinatra/test/integration_test.rb +87 -0
- data/vendor/sinatra/test/less_test.rb +69 -0
- data/vendor/sinatra/test/liquid_test.rb +59 -0
- data/vendor/sinatra/test/mapped_error_test.rb +305 -0
- data/vendor/sinatra/test/markaby_test.rb +80 -0
- data/vendor/sinatra/test/markdown_test.rb +82 -0
- data/vendor/sinatra/test/middleware_test.rb +68 -0
- data/vendor/sinatra/test/nokogiri_test.rb +67 -0
- data/vendor/sinatra/test/public/favicon.ico +0 -0
- data/vendor/sinatra/test/rabl_test.rb +89 -0
- data/vendor/sinatra/test/rack_test.rb +45 -0
- data/vendor/sinatra/test/radius_test.rb +59 -0
- data/vendor/sinatra/test/rdoc_test.rb +66 -0
- data/vendor/sinatra/test/readme_test.rb +120 -0
- data/vendor/sinatra/test/request_test.rb +45 -0
- data/vendor/sinatra/test/response_test.rb +64 -0
- data/vendor/sinatra/test/result_test.rb +76 -0
- data/vendor/sinatra/test/route_added_hook_test.rb +59 -0
- data/vendor/sinatra/test/routing_test.rb +1175 -0
- data/vendor/sinatra/test/sass_test.rb +116 -0
- data/vendor/sinatra/test/scss_test.rb +89 -0
- data/vendor/sinatra/test/server_test.rb +48 -0
- data/vendor/sinatra/test/settings_test.rb +561 -0
- data/vendor/sinatra/test/sinatra_test.rb +12 -0
- data/vendor/sinatra/test/slim_test.rb +84 -0
- data/vendor/sinatra/test/static_test.rb +219 -0
- data/vendor/sinatra/test/streaming_test.rb +149 -0
- data/vendor/sinatra/test/templates_test.rb +333 -0
- data/vendor/sinatra/test/textile_test.rb +65 -0
- data/vendor/sinatra/test/views/a/in_a.str +1 -0
- data/vendor/sinatra/test/views/ascii.erb +2 -0
- data/vendor/sinatra/test/views/b/in_b.str +1 -0
- data/vendor/sinatra/test/views/calc.html.erb +1 -0
- data/vendor/sinatra/test/views/error.builder +3 -0
- data/vendor/sinatra/test/views/error.erb +3 -0
- data/vendor/sinatra/test/views/error.haml +3 -0
- data/vendor/sinatra/test/views/error.sass +2 -0
- data/vendor/sinatra/test/views/explicitly_nested.str +1 -0
- data/vendor/sinatra/test/views/foo/hello.test +1 -0
- data/vendor/sinatra/test/views/hello.builder +1 -0
- data/vendor/sinatra/test/views/hello.coffee +1 -0
- data/vendor/sinatra/test/views/hello.creole +1 -0
- data/vendor/sinatra/test/views/hello.erb +1 -0
- data/vendor/sinatra/test/views/hello.haml +1 -0
- data/vendor/sinatra/test/views/hello.less +5 -0
- data/vendor/sinatra/test/views/hello.liquid +1 -0
- data/vendor/sinatra/test/views/hello.mab +1 -0
- data/vendor/sinatra/test/views/hello.md +1 -0
- data/vendor/sinatra/test/views/hello.nokogiri +1 -0
- data/vendor/sinatra/test/views/hello.rabl +2 -0
- data/vendor/sinatra/test/views/hello.radius +1 -0
- data/vendor/sinatra/test/views/hello.rdoc +1 -0
- data/vendor/sinatra/test/views/hello.sass +2 -0
- data/vendor/sinatra/test/views/hello.scss +3 -0
- data/vendor/sinatra/test/views/hello.slim +1 -0
- data/vendor/sinatra/test/views/hello.str +1 -0
- data/vendor/sinatra/test/views/hello.test +1 -0
- data/vendor/sinatra/test/views/hello.textile +1 -0
- data/vendor/sinatra/test/views/hello.wlang +1 -0
- data/vendor/sinatra/test/views/hello.yajl +1 -0
- data/vendor/sinatra/test/views/layout2.builder +3 -0
- data/vendor/sinatra/test/views/layout2.erb +2 -0
- data/vendor/sinatra/test/views/layout2.haml +2 -0
- data/vendor/sinatra/test/views/layout2.liquid +2 -0
- data/vendor/sinatra/test/views/layout2.mab +2 -0
- data/vendor/sinatra/test/views/layout2.nokogiri +3 -0
- data/vendor/sinatra/test/views/layout2.rabl +3 -0
- data/vendor/sinatra/test/views/layout2.radius +2 -0
- data/vendor/sinatra/test/views/layout2.slim +3 -0
- data/vendor/sinatra/test/views/layout2.str +2 -0
- data/vendor/sinatra/test/views/layout2.test +1 -0
- data/vendor/sinatra/test/views/layout2.wlang +2 -0
- data/vendor/sinatra/test/views/nested.str +1 -0
- data/vendor/sinatra/test/views/utf8.erb +2 -0
- data/vendor/sinatra/test/wlang_test.rb +70 -0
- data/vendor/sinatra/test/yajl_test.rb +86 -0
- metadata +414 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
data/bin/devcenter
ADDED
data/devcenter.gemspec
ADDED
@@ -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
|
data/lib/devcenter.rb
ADDED
@@ -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,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
|