devcenter 1.0.7 → 1.1.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -2
- data/lib/devcenter.rb +1 -0
- data/lib/devcenter/article_file.rb +32 -0
- data/lib/devcenter/cli.rb +13 -0
- data/lib/devcenter/client.rb +78 -3
- data/lib/devcenter/commands.rb +1 -0
- data/lib/devcenter/commands/base.rb +4 -3
- data/lib/devcenter/commands/open.rb +5 -6
- data/lib/devcenter/commands/pull.rb +2 -2
- data/lib/devcenter/commands/push.rb +74 -0
- data/lib/devcenter/helpers.rb +8 -0
- data/lib/devcenter/previewer/views/article.erb +3 -13
- data/lib/devcenter/previewer/web_app.rb +1 -17
- data/lib/devcenter/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 592c9194f39d3e37bf58ec40370e83df3f157c14
|
4
|
+
data.tar.gz: d2f34c3bdf1ac94219fd567a821e01b98306aeff
|
5
5
|
!binary "U0hBNTEy":
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7ec004380727c6a826b2a8efab3610b5704734fdbb09054b3419c315eac0aa7fe6d7cf5a8a88078afbf722cfd5b09642edba40325dd4e7988b118ace13e12f2
|
7
|
+
data.tar.gz: 001e0ec246f33908a0632579efb6f55d20905d2a74558ca4bef740a6d514d264fac6c94a70433d7e5618a75139d80082f1c5a5e785124b27a4d73f67e19e9691
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# Dev Center CLI
|
2
2
|
|
3
3
|
CLI to interact with Heroku's Dev Center
|
4
4
|
|
@@ -30,6 +30,12 @@ This will save an `article-slug.md` text file in your local directory. The file
|
|
30
30
|
|
31
31
|
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.
|
32
32
|
|
33
|
+
### Update an article in Dev Center from a local file
|
34
|
+
|
35
|
+
$ devcenter push dynos
|
36
|
+
|
37
|
+
This will save the title and content from your local article in Dev Center. You'll be asked to authenticate to verify that you have permissions to update the article.
|
38
|
+
|
33
39
|
### Help
|
34
40
|
|
35
41
|
Get available commands
|
@@ -48,4 +54,4 @@ If you have a Dev Center instance, you can point your CLI to it by setting the `
|
|
48
54
|
|
49
55
|
See LICENSE.txt file.
|
50
56
|
|
51
|
-
The `preview` command uses the [Font Awesome](http://fontawesome.io/) vector icons, which have their own [License](https://github.com/FortAwesome/Font-Awesome#license).
|
57
|
+
The `preview` command uses the [Font Awesome](http://fontawesome.io/) vector icons, which have their own [License](https://github.com/FortAwesome/Font-Awesome#license).
|
data/lib/devcenter.rb
CHANGED
@@ -4,5 +4,6 @@ require_relative 'devcenter/logger'
|
|
4
4
|
require_relative 'devcenter/helpers'
|
5
5
|
require_relative 'devcenter/client'
|
6
6
|
require_relative 'devcenter/coderay_extensions'
|
7
|
+
require_relative 'devcenter/article_file'
|
7
8
|
require_relative 'devcenter/previewer'
|
8
9
|
require_relative 'devcenter/cli'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'devcenter-parser'
|
2
|
+
|
3
|
+
module Devcenter
|
4
|
+
|
5
|
+
class ArticleFile
|
6
|
+
|
7
|
+
attr_reader :metadata, :html, :content, :parsing_error, :toc
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
@metadata = opts[:metadata] || OpenStruct.new
|
11
|
+
@content = opts[:content] || ''
|
12
|
+
markdown_flavour = @metadata.markdown_flavour || :maruku
|
13
|
+
begin
|
14
|
+
@html = ::DevcenterParser.to_html(@content, markdown_flavour.to_sym)
|
15
|
+
rescue Exception => e
|
16
|
+
@parsing_error = e.to_s
|
17
|
+
@html = ''
|
18
|
+
end
|
19
|
+
@toc = Nokogiri::HTML(@html).search('h2')
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.read(src_path)
|
23
|
+
src = IO.read(src_path)
|
24
|
+
metadata_yaml, content = src.split(/\r*\n\r*\n/, 2)
|
25
|
+
metadata = OpenStruct.new YAML.load(metadata_yaml)
|
26
|
+
markdown_flavour = metadata.markdown_flavour || :maruku
|
27
|
+
new(metadata: metadata, content: content)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/lib/devcenter/cli.rb
CHANGED
@@ -36,6 +36,19 @@ command :pull do |c|
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
command :push do |c|
|
40
|
+
c.syntax = 'devcenter push [options]'
|
41
|
+
c.summary = 'Submit the content of a local article to Dev Center'
|
42
|
+
c.description = c.summary
|
43
|
+
c.example 'devcenter push process-model.md', 'Submits the content of the local "process-model.md" file to the article with the "process-model" slug in Dev Center'
|
44
|
+
c.option '--debug', 'Output internal log to help debugging'
|
45
|
+
c.action do |args, options|
|
46
|
+
options.default :debug => false
|
47
|
+
Devcenter::Logger.active = options.debug
|
48
|
+
Devcenter::Commands::Push.run(args[0], options.force)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
39
52
|
command :preview do |c|
|
40
53
|
c.syntax = 'devcenter preview [options]'
|
41
54
|
c.summary = 'Opens a live preview for a given article file'
|
data/lib/devcenter/client.rb
CHANGED
@@ -1,22 +1,97 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require 'excon'
|
3
|
+
require 'base64'
|
3
4
|
|
4
5
|
module Devcenter
|
5
6
|
|
6
7
|
module Client
|
8
|
+
|
9
|
+
class Response
|
10
|
+
def initialize(response)
|
11
|
+
@status, @body, @headers = response.status, response.body, response.headers
|
12
|
+
end
|
13
|
+
|
14
|
+
def ok?
|
15
|
+
[200, 201].include?(@status)
|
16
|
+
end
|
17
|
+
|
18
|
+
def redirect?
|
19
|
+
[301, 302].include?(@status)
|
20
|
+
end
|
21
|
+
|
22
|
+
def not_found?
|
23
|
+
@status == 404
|
24
|
+
end
|
25
|
+
|
26
|
+
def access_denied?
|
27
|
+
@status == 401
|
28
|
+
end
|
29
|
+
|
30
|
+
def body
|
31
|
+
JSON.parse(@body)
|
32
|
+
end
|
33
|
+
|
34
|
+
def location
|
35
|
+
@headers['Location']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
7
39
|
include Devcenter::Helpers
|
8
40
|
extend self
|
9
41
|
|
42
|
+
def validate_article(token, article_id, article_params)
|
43
|
+
form = URI.encode_www_form(article_params)
|
44
|
+
auth_request(:post, token, path: validate_article_path(article_id), body: form, :headers => { "Content-Type" => "application/x-www-form-urlencoded" })
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_article(token, article_id, article_params)
|
48
|
+
form = URI.encode_www_form(article_params)
|
49
|
+
auth_request(:put, token, path: update_article_path(article_id), body: form, :headers => { "Content-Type" => "application/x-www-form-urlencoded" })
|
50
|
+
end
|
51
|
+
|
52
|
+
def response(request_call)
|
53
|
+
Response.new request_call
|
54
|
+
end
|
55
|
+
|
10
56
|
def head(args)
|
11
|
-
client.head(args)
|
57
|
+
response client.head(args)
|
12
58
|
end
|
13
59
|
|
14
60
|
def get(args)
|
15
|
-
client.get(args)
|
61
|
+
response client.get(args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def put(args)
|
65
|
+
response client.put(args)
|
66
|
+
end
|
67
|
+
|
68
|
+
def auth_request(method, token, args)
|
69
|
+
args[:headers] ||= {}
|
70
|
+
args[:headers].merge!(default_headers)
|
71
|
+
args[:headers]["Authorization"] = "Basic #{encode64(token)}"
|
72
|
+
response client.send(method, args)
|
16
73
|
end
|
17
74
|
|
18
75
|
def client
|
19
|
-
@client ||= Excon.new(devcenter_base_url, :headers =>
|
76
|
+
@client ||= Excon.new(devcenter_base_url, :headers => default_headers)
|
77
|
+
end
|
78
|
+
|
79
|
+
def default_headers
|
80
|
+
{'User-agent' => 'DevCenterCLI'}
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_oauth_token(email, password)
|
84
|
+
auth = encode64("#{email}:#{password}")
|
85
|
+
response Excon.post('https://api.heroku.com/oauth/authorizations',
|
86
|
+
headers: {
|
87
|
+
"Content-Type" => "application/json",
|
88
|
+
"Accept" => "application/vnd.heroku+json; version=3",
|
89
|
+
"Authorization" => "Basic #{auth}"
|
90
|
+
})
|
91
|
+
end
|
92
|
+
|
93
|
+
def encode64(str)
|
94
|
+
Base64.encode64(str).strip
|
20
95
|
end
|
21
96
|
end
|
22
97
|
|
data/lib/devcenter/commands.rb
CHANGED
@@ -24,6 +24,7 @@ module Devcenter::Commands
|
|
24
24
|
validate
|
25
25
|
if @validation_errors.any?
|
26
26
|
@validation_errors.each{ |e| say e }
|
27
|
+
abort
|
27
28
|
else
|
28
29
|
run
|
29
30
|
end
|
@@ -36,7 +37,8 @@ module Devcenter::Commands
|
|
36
37
|
|
37
38
|
def article_not_found!(slug)
|
38
39
|
message = ["No #{slug} article found."]
|
39
|
-
|
40
|
+
response = Devcenter::Client.get(:path => search_api_path, :query => { :q => slug, :source => 'devcenter-cli' })
|
41
|
+
suggestions = response.body['devcenter']
|
40
42
|
suggestions.select!{ |s| article_url?(s['full_url']) }
|
41
43
|
suggestions.each{ |s| s['slug'] = slug_from_article_url(s['full_url']) }
|
42
44
|
unless suggestions.empty?
|
@@ -46,8 +48,7 @@ module Devcenter::Commands
|
|
46
48
|
message << " %-#{longest}s # %s" % [suggestion['slug'], suggestion['title']]
|
47
49
|
end
|
48
50
|
end
|
49
|
-
|
50
|
-
exit
|
51
|
+
abort message.join("\n")
|
51
52
|
end
|
52
53
|
|
53
54
|
end
|
@@ -16,15 +16,14 @@ module Devcenter::Commands
|
|
16
16
|
def run
|
17
17
|
path = article_path(@slug)
|
18
18
|
log "Connecting to #{path}"
|
19
|
-
|
20
|
-
|
21
|
-
when 200
|
19
|
+
response = Devcenter::Client.head(:path => path)
|
20
|
+
if response.ok?
|
22
21
|
log "Page found, opening"
|
23
22
|
launchy = Launchy.open(devcenter_base_url + path)
|
24
23
|
launchy.join if launchy.respond_to?(:join)
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
elsif response.redirect?
|
25
|
+
abort "Redirected to #{response.location}"
|
26
|
+
elsif response.not_found?
|
28
27
|
article_not_found!(@slug)
|
29
28
|
end
|
30
29
|
end
|
@@ -16,10 +16,10 @@ module Devcenter::Commands
|
|
16
16
|
|
17
17
|
def run
|
18
18
|
response = Devcenter::Client.get(path: article_api_path(@slug))
|
19
|
-
article_received = response.
|
19
|
+
article_received = response.ok? && response.body['article'] && response.body['article']['id']
|
20
20
|
article_not_found!(@slug) unless article_received
|
21
21
|
|
22
|
-
article =
|
22
|
+
article = response.body['article']
|
23
23
|
metadata = {'title' => article['title'], 'id' => article['id'], 'markdown_flavour' => article['markdown_flavour']}
|
24
24
|
file_path = md_file_path(@slug)
|
25
25
|
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Devcenter::Commands
|
2
|
+
|
3
|
+
class Push < Base
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@slug = args[0].to_s.gsub(/.md\z/, '') # maybe the user provides the filename by mistake
|
7
|
+
@md_path = md_file_path(@slug)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate
|
12
|
+
empty_slug = @slug.nil? || @slug.to_s.strip.empty?
|
13
|
+
file_exists = !empty_slug && File.exists?(@md_path)
|
14
|
+
if empty_slug
|
15
|
+
@validation_errors << 'Please provide an article slug'
|
16
|
+
elsif !file_exists
|
17
|
+
@validation_errors << "Can't find #{@md_path} file - you may want to `devcenter pull #{@slug}`"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
@article = ::Devcenter::ArticleFile.read(@md_path)
|
23
|
+
if @article.parsing_error
|
24
|
+
abort "The content of #{@md_path} can't be parsed properly, fix it and try again."
|
25
|
+
end
|
26
|
+
say 'Authenticate with your Heroku account:'
|
27
|
+
email = ask('Email: ')
|
28
|
+
password = ask('Password: ') { |q| q.echo = '*' }
|
29
|
+
response = Devcenter::Client.get_oauth_token(email, password)
|
30
|
+
if response.access_denied?
|
31
|
+
abort "Authentication error: bad credentials. Please try again."
|
32
|
+
elsif response.ok?
|
33
|
+
token = response.body['access_token']['token']
|
34
|
+
push_article(token) if validate_article(token)
|
35
|
+
else
|
36
|
+
abort "Authentication error: #{response.body['message']}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_article(token)
|
41
|
+
article_id = @article.metadata.id
|
42
|
+
article_params = {
|
43
|
+
'article[content]' => @article.content,
|
44
|
+
'article[title]' => @article.metadata.title,
|
45
|
+
'article[parser]' => @article.metadata.parser || @article.metadata.markdown_flavour
|
46
|
+
}
|
47
|
+
response = Devcenter::Client.validate_article(token, article_id, article_params)
|
48
|
+
errors = response.body
|
49
|
+
if errors.any?
|
50
|
+
say "The article \"#{@slug}\" is not valid:"
|
51
|
+
abort errors.to_yaml.gsub(/\A(\-+)\n-/, '')
|
52
|
+
else
|
53
|
+
say "The article \"#{@slug}\" is valid."
|
54
|
+
end
|
55
|
+
errors.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def push_article(token)
|
59
|
+
article_id = @article.metadata.id
|
60
|
+
article_params = {
|
61
|
+
'article[content]' => @article.content,
|
62
|
+
'article[title]' => @article.metadata.title,
|
63
|
+
'article[parser]' => @article.metadata.parser || @article.metadata.markdown_flavour
|
64
|
+
}
|
65
|
+
response = Devcenter::Client.update_article(token, article_id, article_params)
|
66
|
+
if response.ok?
|
67
|
+
say "\"#{@slug}\" pushed successfully."
|
68
|
+
else
|
69
|
+
error = response.body['error']
|
70
|
+
abort "Error pushing \"#{@slug}\": #{error}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/devcenter/helpers.rb
CHANGED
@@ -16,6 +16,14 @@ module Devcenter::Helpers
|
|
16
16
|
"/articles.json"
|
17
17
|
end
|
18
18
|
|
19
|
+
def validate_article_path(id)
|
20
|
+
"/api/v1/private/articles/#{id}/validate.json"
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_article_path(id)
|
24
|
+
"/api/v1/private/articles/#{id}.json"
|
25
|
+
end
|
26
|
+
|
19
27
|
def article_url?(url)
|
20
28
|
escaped_base_url = devcenter_base_url.gsub('/','\\/')
|
21
29
|
url.match(/\A#{escaped_base_url}\/articles\/.+/)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<section class="main">
|
2
2
|
|
3
|
-
<article class="article autolink
|
3
|
+
<article class="article autolink">
|
4
4
|
|
5
5
|
<header>
|
6
6
|
<h1 class="doc"><%= @article.metadata.title %></h1>
|
@@ -23,24 +23,14 @@
|
|
23
23
|
</section>
|
24
24
|
|
25
25
|
<%= @article.html %>
|
26
|
-
<% elsif @article.
|
26
|
+
<% elsif @article.parsing_error %>
|
27
27
|
<div class="cli-preview-error">
|
28
28
|
<h4>Parsing error:</h4>
|
29
29
|
<p>
|
30
|
-
<%= @article.
|
30
|
+
<%= @article.parsing_error %>
|
31
31
|
</p>
|
32
32
|
</div>
|
33
33
|
<% end %>
|
34
|
-
|
35
|
-
<form class="cli-save" action="<%= devcenter_base_url %>/admin/articles/cli-edit/<%= @article.metadata.id %>" method="POST">
|
36
|
-
<input name="_method" type="hidden" value="put">
|
37
|
-
<input type="hidden" name="article[title]" value="<%= @article.metadata.title %>">
|
38
|
-
<input type="hidden" name="article[markdown_flavour]" value="<%= @article.metadata.markdown_flavour %>">
|
39
|
-
<textarea style="display:none" name="article[content]">
|
40
|
-
<%= CGI.escapeHTML(@article.content.to_s.lstrip) %>
|
41
|
-
</textarea>
|
42
|
-
<input type="submit" value="Save in Dev Center">
|
43
|
-
</form>
|
44
34
|
</div>
|
45
35
|
|
46
36
|
</article>
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'sinatra'
|
2
2
|
require 'rack/highlighter'
|
3
|
-
require 'devcenter-parser'
|
4
3
|
|
5
4
|
module Devcenter::Previewer
|
6
5
|
|
@@ -51,8 +50,7 @@ module Devcenter::Previewer
|
|
51
50
|
src_path = File.join(Dir.pwd, "#{params[:slug]}.md")
|
52
51
|
if File.exists?(src_path)
|
53
52
|
log "Parsing"
|
54
|
-
@article =
|
55
|
-
@article.toc = Nokogiri::HTML(@article.html).search('h2')
|
53
|
+
@article = Devcenter::ArticleFile.read(src_path)
|
56
54
|
@page_title = @article.metadata.title
|
57
55
|
log "Serving"
|
58
56
|
erb :article
|
@@ -63,20 +61,6 @@ module Devcenter::Previewer
|
|
63
61
|
end
|
64
62
|
end
|
65
63
|
|
66
|
-
def parse_article(src_path)
|
67
|
-
article = OpenStruct.new
|
68
|
-
src = IO.read(src_path)
|
69
|
-
metadata_yaml, article.content = src.split(/\r*\n\r*\n/, 2)
|
70
|
-
article.metadata = OpenStruct.new YAML.load(metadata_yaml)
|
71
|
-
markdown_flavour = article.metadata.markdown_flavour || :maruku
|
72
|
-
begin
|
73
|
-
article.html = ::DevcenterParser.to_html(article.content, markdown_flavour.to_sym)
|
74
|
-
rescue Exception => e
|
75
|
-
article.error = e.to_s
|
76
|
-
end
|
77
|
-
article
|
78
|
-
end
|
79
|
-
|
80
64
|
def self.send_server_event
|
81
65
|
Devcenter::Logger.log "Serving server side event to #{settings.connections.size} connections"
|
82
66
|
settings.connections.each do |conn|
|
data/lib/devcenter/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devcenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raul Murciano
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-12-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: listen
|
@@ -210,6 +210,7 @@ files:
|
|
210
210
|
- bin/devcenter
|
211
211
|
- devcenter.gemspec
|
212
212
|
- lib/devcenter.rb
|
213
|
+
- lib/devcenter/article_file.rb
|
213
214
|
- lib/devcenter/cli.rb
|
214
215
|
- lib/devcenter/client.rb
|
215
216
|
- lib/devcenter/coderay_extensions.rb
|
@@ -218,6 +219,7 @@ files:
|
|
218
219
|
- lib/devcenter/commands/open.rb
|
219
220
|
- lib/devcenter/commands/preview.rb
|
220
221
|
- lib/devcenter/commands/pull.rb
|
222
|
+
- lib/devcenter/commands/push.rb
|
221
223
|
- lib/devcenter/gem_version_checker.rb
|
222
224
|
- lib/devcenter/helpers.rb
|
223
225
|
- lib/devcenter/logger.rb
|
@@ -293,9 +295,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
293
295
|
version: '0'
|
294
296
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
295
297
|
requirements:
|
296
|
-
- - ! '
|
298
|
+
- - ! '>'
|
297
299
|
- !ruby/object:Gem::Version
|
298
|
-
version:
|
300
|
+
version: 1.3.1
|
299
301
|
requirements: []
|
300
302
|
rubyforge_project:
|
301
303
|
rubygems_version: 2.0.3
|