middleman-medium_export 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 70a0f8d944340107139756124aa63973a3f71c686fcee927ea63a830e8971f22
4
+ data.tar.gz: 8cca4d27765a84ffaedb871012d3f46fe1b2ec33f4d8874155a6c4a593829c17
5
+ SHA512:
6
+ metadata.gz: c6884c14ae7550558aa91a0ce8d80d548f1a37c5305b6d291ccae6910cc79a7ece792218b36cdd3a730d1f419163edf0f9743a3930997a95134947c1c6b64570
7
+ data.tar.gz: 7178bd55aab8c1524da4464caf0a91189f3a50eaa9044b0a922050536959afeafe2e1a30757850199ee7c53605c48b3ab16cac75bc136005eb762e1767382a8d
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,77 @@
1
+ # Middleman Medium Export
2
+
3
+ A Middleman extension that adds a cli command to export your articles to medium
4
+
5
+ # Getting Started
6
+
7
+ Add the gem to your gemfile
8
+
9
+ ```ruby
10
+ gem "middleman-medium_export"
11
+ ```
12
+
13
+ Activate it in your `config.rb`
14
+
15
+ ```ruby
16
+ activate :medium_export do |export|
17
+ export.api_token = 'your_api_token'
18
+
19
+ # export.template_path = 'path_to_template'
20
+ # export.template_position = :top
21
+ # export.publish_status = :draft
22
+ end
23
+ ```
24
+
25
+ ## Options
26
+
27
+ Extension provides several options
28
+
29
+ `api_token` - required, self issued api token to post your articles
30
+
31
+ `template_path` - optinal, path to partial to add to your article
32
+ `template_position` - optional. Position of your template. Possible values are: `:top` and `:bottom`. Default is `:bottom`
33
+ `publish_status` - optional. Status of your articles, when they are exported. Possible values are: `public`, `draft`, or `unlisted`. Default is `:draft`
34
+
35
+ ## Export
36
+
37
+ ### Interactivly
38
+
39
+ To chose article to export interactivlt
40
+
41
+ ```ruby
42
+ be middleman medium_export -m interactive
43
+ ```
44
+
45
+ ### Only last article
46
+
47
+ ```ruby
48
+ be middleman medium_export
49
+ ```
50
+
51
+ or
52
+
53
+ ```ruby
54
+ be middleman medium_export -m last
55
+ ```
56
+
57
+ ### All articles
58
+
59
+ ```ruby
60
+ be middleman medium_export -m all
61
+ ```
62
+
63
+ ## Obtaining Api Token
64
+
65
+ You used to be able to create an `api_token` yourself, but due to increased spam activity from API, this feature is now restricted. You need to email support with request for an API Token. It usually takes from a couple of hours to a day. Visit official [documentaion](https://github.com/Medium/medium-api-docs#22-self-issued-access-tokens) for more info.
66
+
67
+ # Contribution
68
+
69
+ Pull requests and issues are very welcome
70
+
71
+ To create a pull request
72
+
73
+ * Clone repo
74
+ * Run tests to see, that all is fine `be rake test`
75
+ * Implement feature
76
+ * Add tests
77
+
@@ -0,0 +1,8 @@
1
+ require "middleman-core"
2
+
3
+ Middleman::Extensions.register :medium_export do
4
+ require "middleman-medium_export/extension"
5
+ require "middleman-medium_export/commands/export"
6
+
7
+ MediumExport
8
+ end
@@ -0,0 +1,53 @@
1
+ require 'httparty'
2
+
3
+ class MediumExport::ApiClient
4
+ include HTTParty
5
+
6
+ attr_reader :auth, :publish_status
7
+
8
+ base_uri 'https://api.medium.com/v1/'
9
+
10
+ headers 'Content-Type': 'application/json'
11
+ headers 'Accept': 'application/json'
12
+
13
+ # raise_on (400..599).to_a
14
+
15
+ def initialize(api_token:, publish_status:)
16
+ @auth = { Authorization: "Bearer #{api_token}" }
17
+ @publish_status = publish_status
18
+ end
19
+
20
+ def publish(content:, title:, tags:)
21
+ response = self.class.post(
22
+ "/users/#{id}/posts",
23
+ headers: auth,
24
+ body: {
25
+ "title": title,
26
+ "contentFormat": "html",
27
+ "content": content,
28
+ "tags": tags,
29
+ "publishStatus": publish_status
30
+ }.to_json
31
+ )
32
+ end
33
+
34
+ def upload_image(image:)
35
+ response = self.class.post(
36
+ "https://api.medium.com/v1/images",
37
+ headers: auth,
38
+ body: {
39
+ "image": image
40
+ }
41
+ )
42
+ end
43
+
44
+ def me
45
+ self.class.get('/me', headers: auth)
46
+ end
47
+
48
+ private
49
+
50
+ def id
51
+ @id ||= me['data']['id']
52
+ end
53
+ end
@@ -0,0 +1,36 @@
1
+ class MediumExport::ArticlesFilter
2
+ attr_reader :articles, :shell
3
+
4
+ def initialize(articles:, shell:)
5
+ @articles = articles
6
+ @shell = shell
7
+ end
8
+
9
+ def last
10
+ [articles.first]
11
+ end
12
+
13
+ def all
14
+ articles
15
+ end
16
+
17
+ def interactive
18
+ articles.each_with_object([]) do |article, acc|
19
+ case ask_to_include(article.title)
20
+ when 'Y' then acc.push(article)
21
+ when 'S' then acc.push(article) and break(acc)
22
+ when 'Q' then break(acc)
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def ask_to_include(title)
30
+ shell.say(%Q(Do you want to publish "#{title}"\n).freeze)
31
+ shell.say(" Y - yes and continue\n S - yes and quit".freeze, :green)
32
+ shell.say(" N - no and continue\n Q - no and quit\n".freeze, :red)
33
+
34
+ shell.ask("\n ", :magenta, limited_to: %w(Y S N Q).freeze)
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ require 'middleman-core/cli'
2
+ require 'middleman-blog/uri_templates'
3
+ require 'middleman-medium_export/articles_filter'
4
+ require 'middleman-medium_export/content'
5
+ require 'middleman-medium_export/publisher'
6
+
7
+ module Middleman
8
+
9
+ module Cli
10
+
11
+ ##
12
+ # This class provides an "medium_export" command for the middleman CLI.
13
+ #
14
+ # @usage bundle exec middleman medium_export
15
+ # @usage bundle exec middleman medium_export --help
16
+ # @usage bundle exec middleman medium_export --mode last
17
+ # @usage bundle exec middleman medium_export --mode interactive
18
+ #
19
+ ##
20
+ class Export < ::Thor::Group
21
+ include Thor::Actions
22
+ include Blog::UriTemplates
23
+
24
+ check_unknown_options!
25
+
26
+ class_option "blog",
27
+ aliases: "-b",
28
+ desc: "The name of the blog to create the post inside (for multi-blog apps, defaults to the only blog in single-blog apps)"
29
+
30
+ class_option "mode",
31
+ aliases: "-m",
32
+ enum: %w[last all interactive],
33
+ default: 'last',
34
+ desc: "Chose which articles should be published"
35
+
36
+
37
+ def medium_export
38
+ articles = blog.data.articles.sort_by { |a| -a.date.to_i }
39
+
40
+ filtered_articles = MediumExport::ArticlesFilter.
41
+ new(articles: articles, shell: shell).
42
+ public_send(options.mode)
43
+
44
+ content = filtered_articles.map do |article|
45
+ MediumExport::Content.new(article: article, template: export_extension.template)
46
+ end
47
+
48
+ MediumExport::Publisher.new(
49
+ api_client: export_extension.api_client, content: content, shell: shell).call
50
+ end
51
+
52
+ private
53
+
54
+ def export_extension
55
+ @export_extension ||= app.extensions[:medium_export]
56
+ end
57
+
58
+ def app
59
+ @app ||= ::Middleman::Application.new
60
+ end
61
+
62
+ def blog
63
+ @blog ||= if options[:blog]
64
+ app.extensions[:blog].find { | k, v | v.options[:name] == options[ :blog ] }.last
65
+ else
66
+ app.extensions[:blog].values.first
67
+ end
68
+ end
69
+
70
+ # Add to CLI
71
+ Base.register(self, 'medium_export', 'medium_export [options]', 'Export one or more articles to middleman')
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,58 @@
1
+ require 'nokogiri'
2
+
3
+ class MediumExport::Content
4
+ Image = Struct.new(:src, :location)
5
+
6
+ attr_reader :article, :template
7
+
8
+ def initialize(article:, template: nil)
9
+ @article = article
10
+ @template = template
11
+ end
12
+
13
+ def html
14
+ @content ||= begin
15
+ template ? body_with_template : article.body
16
+ end
17
+ end
18
+
19
+ def markdown
20
+ @markdown ||= File.read(article.source_file)
21
+ end
22
+
23
+ def title
24
+ article.title
25
+ end
26
+
27
+ def tags
28
+ article.tags
29
+ end
30
+
31
+ def local_images
32
+ @images ||= Nokogiri::HTML(html).search('img').map do |img|
33
+ src = img.attributes['src'].value
34
+ next if src.start_with?('http')
35
+
36
+ Image.new(src, File.join(source_dir, src))
37
+ end.compact
38
+ end
39
+
40
+ private
41
+
42
+ def body_with_template
43
+ template_html = template_context.render(nil, template.path, { locals: {}})
44
+ template.position == :top ? "#{template_html}#{article.body}" : "#{article.body}#{template_html}"
45
+ end
46
+
47
+ def source_dir
48
+ @source_dir ||= app.source_dir
49
+ end
50
+
51
+ def template_context
52
+ @template_context ||= app.generic_template_context
53
+ end
54
+
55
+ def app
56
+ @app ||= article.blog_data.controller.app
57
+ end
58
+ end
@@ -0,0 +1,66 @@
1
+ # Require core library
2
+ require 'middleman-core'
3
+
4
+ # Extension namespace
5
+ class MediumExport < ::Middleman::Extension
6
+ require 'middleman-medium_export/api_client'
7
+ require 'middleman-medium_export/template'
8
+
9
+ ApiTokenMissing = Class.new(ArgumentError)
10
+ InvalidTemplatePosition = Class.new(ArgumentError)
11
+ InvalidPublishStatus = Class.new(ArgumentError)
12
+
13
+ TEMPLATES_POSSITIONS = %w[bottom top].freeze
14
+ PUBLISH_STATUSES = %i[public draft unlisted].freeze
15
+
16
+ option :api_token, nil, 'Medium API Token'
17
+ option :template_path, nil, 'HTML template to append to the end of each article'
18
+ option :template_position, :bottom
19
+ option :publish_status, :draft
20
+ option :tags_field, :tags
21
+
22
+ def initialize(app, options_hash={}, &block)
23
+ super
24
+
25
+ check_api_token!
26
+ check_template_positions!
27
+ check_publish_statuses!
28
+ end
29
+
30
+ def api_client
31
+ @api_client ||= ApiClient.new(options.to_h.slice(:api_token, :publish_status))
32
+ end
33
+
34
+ def template
35
+ return unless options.template_path
36
+
37
+ Template.new(options.to_h.slice(:template_path, :template_position))
38
+ end
39
+
40
+ private
41
+
42
+ def check_api_token!
43
+ return unless options.api_token.to_s.empty?
44
+
45
+ error_msg = "Please, provide an api_token option. To obtain api_token refer to\n" \
46
+ "https://help.medium.com/hc/en-us/articles/213480228-Get-integration-token\n\n"
47
+ raise ApiTokenMissing, error_msg
48
+ end
49
+
50
+ def check_template_positions!
51
+ position = options.template_position.to_s
52
+ return if position.empty? || TEMPLATES_POSSITIONS.include?(position)
53
+
54
+ error_msg = "Invalid template_position: #{options.template_position}.\n" \
55
+ "Possible template positions are: #{TEMPLATES_POSSITIONS.join(", ")}\n\n"
56
+ raise InvalidTemplatePosition, error_msg
57
+ end
58
+
59
+ def check_publish_statuses!
60
+ return if PUBLISH_STATUSES.include?(options.publish_status.to_sym)
61
+
62
+ error_msg = "Invalid publish_status: #{options.publish_status}.\n" \
63
+ "Possible publish statuses are: #{PUBLISH_STATUSES.join(", ")}\n\n"
64
+ raise InvalidPublishStatus, error_msg
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ class MediumExport::Publisher
2
+ attr_reader :content, :shell, :api_client
3
+
4
+ def initialize(content:, shell:, api_client:)
5
+ @content = content
6
+ @shell = shell
7
+ @api_client = api_client
8
+ end
9
+
10
+ def call
11
+ shell.say("Preparing to publish #{content.size} articles") if content.size > 1
12
+
13
+ content.each do |article|
14
+ publish(article)
15
+ shell.say(%Q(Published: "#{article.title}"), :green)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def publish(article)
22
+ updated_html = article.local_images.inject(article.html) do |html, image|
23
+ response = api_client.upload_image(image: File.open(image.location))
24
+ url = response['data']['url'] # it may contain errors {"errors"=>[{"message"=>"Token was invalid.", "code"=>6003}]}
25
+ html.gsub(image.src, url)
26
+ end
27
+
28
+ api_client.publish(content: updated_html, tags: article.tags, title: article.title)
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ class MediumExport::Template
2
+
3
+ attr_reader :path, :position
4
+
5
+ def initialize(template_path:, template_position:)
6
+ @path = template_path
7
+ @position = template_position.to_sym
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: middleman-medium_export
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - TheSmartnik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: middleman-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: middleman-blog
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: httparty
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.16.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.16.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: middleman-cli
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: |
98
+ An extension to export your articles created with
99
+ middleman-blog to medium from a command line.
100
+ email:
101
+ - misharinn@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - MIT-LICENSE
107
+ - README.md
108
+ - lib/middleman-medium_export.rb
109
+ - lib/middleman-medium_export/api_client.rb
110
+ - lib/middleman-medium_export/articles_filter.rb
111
+ - lib/middleman-medium_export/commands/export.rb
112
+ - lib/middleman-medium_export/content.rb
113
+ - lib/middleman-medium_export/extension.rb
114
+ - lib/middleman-medium_export/publisher.rb
115
+ - lib/middleman-medium_export/template.rb
116
+ homepage: https://github.com/TheSmartnik/middleman-medium_export
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.7.6
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: A Middleman extension to export your blogposts to medium
140
+ test_files: []