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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
- metadata.gz: f8cf248735847c0a751bf6947c723d2dcff7245a
4
- data.tar.gz: a4c53ace9bb2948333ba95fee47651129b05c5fa
3
+ metadata.gz: 592c9194f39d3e37bf58ec40370e83df3f157c14
4
+ data.tar.gz: d2f34c3bdf1ac94219fd567a821e01b98306aeff
5
5
  !binary "U0hBNTEy":
6
- metadata.gz: 4cc8bf967f1c7c381bf33417f413a95982bd92b6b6985d9d8749f1b22204af885cb117fd23f0be240495e8d4ad9736facd62b8a96191696cd82f70692ea207e8
7
- data.tar.gz: a7705b6253fb6dade4c4a0739b941485005557c2ba56029dd0a41c49d0ecae98241f0e60153081aaee9c52ab57e39d222a5225353d5ad1a6b3e065d1b2201650
6
+ metadata.gz: f7ec004380727c6a826b2a8efab3610b5704734fdbb09054b3419c315eac0aa7fe6d7cf5a8a88078afbf722cfd5b09642edba40325dd4e7988b118ace13e12f2
7
+ data.tar.gz: 001e0ec246f33908a0632579efb6f55d20905d2a74558ca4bef740a6d514d264fac6c94a70433d7e5618a75139d80082f1c5a5e785124b27a4d73f67e19e9691
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Devcenter
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'
@@ -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 => { 'User-agent' => 'DevCenterCLI'})
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
 
@@ -1,4 +1,5 @@
1
1
  require_relative 'commands/base'
2
2
  require_relative 'commands/open'
3
3
  require_relative 'commands/pull'
4
+ require_relative 'commands/push'
4
5
  require_relative 'commands/preview'
@@ -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
- suggestions = JSON.parse(Devcenter::Client.get(:path => search_api_path, :query => { :q => slug, :source => 'devcenter-cli' }).body)['devcenter']
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
- say message.join("\n")
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
- head = Devcenter::Client.head(:path => path)
20
- case head.status
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
- when 301, 302
26
- say "Redirected to #{head.headers['Location']}"
27
- when 404
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.status == 200 && JSON.parse(response.body)['article'] && JSON.parse(response.body)['article']['id']
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 = JSON.parse(response.body)['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
@@ -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 <% 'contributed' if @article.external_owner? %>">
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.error %>
26
+ <% elsif @article.parsing_error %>
27
27
  <div class="cli-preview-error">
28
28
  <h4>Parsing error:</h4>
29
29
  <p>
30
- <%= @article.error %>
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 = parse_article(src_path)
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|
@@ -1,3 +1,3 @@
1
1
  module Devcenter
2
- VERSION = "1.0.7"
2
+ VERSION = "1.1.0rc1"
3
3
  end
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.0.7
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-10-29 00:00:00.000000000 Z
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: '0'
300
+ version: 1.3.1
299
301
  requirements: []
300
302
  rubyforge_project:
301
303
  rubygems_version: 2.0.3