devcenter 1.0.7 → 1.1.0rc1
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.
- 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
|