jekyll-paspagon 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2d9c1cbf4a393ffc471f25e509d78e1ac3a681f3
4
+ data.tar.gz: eed3f8f170893e1f48ee441e495356d838b3f845
5
+ SHA512:
6
+ metadata.gz: 9c7badcc45eba408df2b4e04576837e25c2d9bcda364026acb2350b9114e6709d9aac523a0c5d5302964205a5f80b14483e82c3632dd91b2387b51ecd2521e9c
7
+ data.tar.gz: a5c921004ed08b4f81802c8ddc9f1e494f048386507971d2f27c4f3ab02e710c7c6687ac1a40dfda3236968c588046865d6fbd0319bae49fa55b5a816a08de16
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright © 2016 Krzysztof Jurewicz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # jekyll-paspagon
2
+
3
+ This [Jekyll](http://jekyllrb.com) plugin allows you to sell blog posts in various formats (HTML, EPUB, AZW3, MOBI, PDF and more) for [Bitcoin](https://bitcoin.org) and [BlackCoin](http://blackcoin.co), using Amazon S3 and [Paspagon](http://paspagon.com).
4
+
5
+ ## Installation
6
+
7
+ ### Install dependencies
8
+
9
+ If you want to sell posts in formats other than HTML, [install Pandoc](http://pandoc.org/installing.html). If you want to sell MOBI, AZW3 or PDF files, install [Calibre](https://calibre-ebook.com/).
10
+
11
+ ### Install plugin
12
+
13
+ Add the `jekyll-paspagon` gem to the `:jekyll_plugins` group in your `Gemfile`:
14
+
15
+ ```ruby
16
+ group :jekyll_plugins do
17
+ gem 'jekyll-paspagon', '~>1'
18
+ end
19
+ ```
20
+
21
+ ### Configure S3 bucket
22
+
23
+ Create a S3 bucket in the `s3-us-west-2` region (Oregon) and [grant Paspagon read access to it](http://paspagon.com/terms-seller/#step-1-grant-us-read-access-to-your-bucket), as described in Paspagon’s terms of service.
24
+
25
+ ### Configure Paspagon
26
+
27
+ To your `_config.yml`, add a section adhering to the following example:
28
+
29
+ ```yaml
30
+ paspagon:
31
+ # By making this entry, you indicate that you accept Paspagon’s terms of
32
+ # service. Be sure that you’ve actually read these terms!
33
+ accept-terms: https://github.com/Paspagon/paspagon.github.io/blob/master/terms-seller.md
34
+ buckets:
35
+ your-bucket-name:
36
+ seller:
37
+ country-code: PL
38
+ email: john.doe@example.com # Email is optional
39
+ payment:
40
+ # This section provides default values that you can override for each post.
41
+ #
42
+ # You can specify prices in BTC, BLK, XAU (troy ounce of gold) and some
43
+ # other currencies (see Paspagon’s terms of service for a complete list).
44
+ # (Paspagon takes only one price into account).
45
+ price:
46
+ USD: 3
47
+ address:
48
+ BTC: 1your-address
49
+ BLK: Byour-address
50
+ # Time (in seconds) after which download link will expire.
51
+ link-expiration-time: 600
52
+ # A name of a bucket which will be used to store S3 request logs.
53
+ logging_bucket: paspagon-logs-foo
54
+ ```
55
+
56
+ ### Set default post configuration
57
+
58
+ Add the bucket and formats configuration to the `default` section in your `_config.yml`. This is optional, but handy.
59
+
60
+ ```yaml
61
+ defaults:
62
+ - scope:
63
+ path: ""
64
+ values:
65
+ formats:
66
+ # This section provides default values that you can override for each post.
67
+ html:
68
+ paid_after: 15 # HTML version will be paid 15 days after publication.
69
+ pdf:
70
+ content_disposition: attachment
71
+ content_type: application/pdf
72
+ paid_before: 2 # PDF version will be paid for the first two days.
73
+ epub: {} # EPUB version will be paid from the beginning.
74
+ bucket: your-bucket-name
75
+ ```
76
+
77
+ Caveat: “default” values in Jekyll are actually not default values that can be simply overridden. Instead they get “deep merged” into post variables. If you want to unset a specific nested value in the `formats` hash, set it to `null`.
78
+
79
+ ### Change post template
80
+
81
+ Inform your readers that you offer alternate post formats by modifying the post template. In a simple form the relevant fragment may look like this:
82
+
83
+ ```liquid
84
+ {% if page.formats %}
85
+ <p>Available formats:
86
+ {% for format in page.format_array %}
87
+ <a href="{{ format.full_url }}">{{ format.name }}</a>{% unless forloop.last %},{% endunless %}
88
+ {% endfor %}
89
+ </p>
90
+ {% endif %}
91
+ ```
92
+
93
+ A more sophisticated example, which uses the `page.excerpt_only` variable (set only when HTML is a paid format and a summary is being rendered):
94
+
95
+ ```liquid
96
+ {% if page.formats %}
97
+ <p>
98
+ {% if page.excerpt_only %}
99
+ Available formats:
100
+ {% else %}
101
+ Alternate formats:
102
+ {% endif %}
103
+ {% for format in page.format_array %}
104
+ {% if format.name != 'HTML' or page.excerpt_only %}
105
+ <a {% if format.paid %}class="paid" {% endif %}href="{{ format.full_url }}">{{ format.name }}</a>{% unless forloop.last %},{% endunless %}
106
+ {% endif %}
107
+ {% endfor %}
108
+ </p>
109
+ {% endif %}
110
+ ```
111
+
112
+ ### Change feed template
113
+
114
+ If you provide a RSS/Atom feed, you may want to ensure that it contains only post excerpts instead of complete contents. Typically, you will need to change `{{ post.content | xml_escape }}` to `{{ post.excerpt | xml_escape }}` in your `feed.xml` template.
115
+
116
+ ### Generate full URLs everywhere
117
+
118
+ If you’re going to sell HTML versions of your posts (which will be hosted on a different domain), you should ensure that links on your website contain the domain. The easiest way to do it is to remove all occurences of `site.url` from the templates (usually in `feed.xml` and `_includes/head.html`) and add the domain to the `baseurl` setting.
119
+
120
+ ## Usage
121
+
122
+ After doing the steps above, paid versions of your posts will be generated automatically.
123
+
124
+ If you specify thresholds like `paid_before`, you will need to run `jekyll build` again after reaching them.
125
+
126
+ You may override formats, buckets, prices and payment addresses for each post by putting the relevant data in the YAML front matter:
127
+
128
+ ```yaml
129
+ layout: post
130
+ title: Foo
131
+ payment:
132
+ price:
133
+ USD: null
134
+ XAU: 3
135
+ address:
136
+ bitcoin: 1Foo
137
+ bucket: foo
138
+ formats:
139
+ epub:
140
+ paid_after: 15
141
+ pdf:
142
+ # Setting this to null means that a post will be paid from the beginning.
143
+ # If you want a format to be free instead, set this to 0.
144
+ paid_before: null
145
+ azw3: {}
146
+ ```
147
+
148
+ Two Jekyll subcommands are available to synchronize paid content with Amazon S3. You need to [specify AWS credentials](http://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs) beforehand.
149
+
150
+ ### paspagon_prepare
151
+
152
+ This command creates necessary buckets, configures logging and log expiration (to 90 days) and sets permissions for Paspagon. It typically needs to be executed only once, after configuring Paspagon.
153
+
154
+ ### paspagon_sync
155
+
156
+ This command uploads missing or updated paid files to S3 and removes ones which are not present locally. It should be ran each time a blog content update is deployed.
157
+
158
+ ## Markdown compatibility
159
+
160
+ jekyll-paspagon uses Pandoc to generate formats other than HTML. The default input format is `markdown_github-hard_line_breaks`. It may be impacted by some site settings like Kramdown’s `hard_wrap`, but the most reliable it to set it explicitly in `_config.yml`:
161
+
162
+ ```yaml
163
+ pandoc:
164
+ input: markdown_phpextra-fenced_code_blocks+strikeout
165
+ ```
166
+
167
+ For more information, refer to [Pandoc’s documentation on Markdown](http://pandoc.org/README.html#pandocs-markdown).
168
+
169
+ ## Versioning
170
+
171
+ This project uses [semantic versioning](http://semver.org/).
172
+
173
+ ## License
174
+
175
+ This software is licensed under [the MIT License](LICENSE).
@@ -0,0 +1,116 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'aws-sdk-resources'
3
+ require 'ffi-xattr'
4
+ require 'jekyll-paspagon/config'
5
+
6
+ module Jekyll
7
+ module Commands
8
+ class Paspagon < Command
9
+ class << self
10
+ def init_with_program(jekyll)
11
+ client = Aws::S3::Client.new(region: 'us-west-2')
12
+ jekyll.command(:paspagon_prepare) do |c|
13
+ c.syntax 'paspagon_prepare'
14
+ c.description 'Create and configure S3 buckets for Paspagon'
15
+
16
+ c.action do |_, options|
17
+ site_options = configuration_from_options(options)
18
+ site = Jekyll::Site.new(site_options)
19
+ config = PaspagonConfig.new(site)
20
+
21
+ logging_bucket_name = config.logging_bucket_name
22
+ logging_bucket = ensure_logging_bucket(logging_bucket_name, client) if logging_bucket_name
23
+ config.buckets.keys.each do |bucket_name|
24
+ prepare_bucket(bucket_name, client, logging_bucket)
25
+ end
26
+ end
27
+ end
28
+
29
+ jekyll.command(:paspagon_sync) do |c|
30
+ c.syntax 'paspagon_sync'
31
+ c.description 'Sync paid files with S3'
32
+
33
+ c.action do |_, options|
34
+ site_options = configuration_from_options(options)
35
+ site = Jekyll::Site.new(site_options)
36
+ config = PaspagonConfig.new(site)
37
+
38
+ paid_dir = config.paid_dest_dir
39
+ Dir["#{paid_dir}/*/"].each do |dir|
40
+ sync_path(dir, client)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def prepare_bucket(name, client, logging_bucket)
47
+ bucket = Aws::S3::Bucket.new(name, client: client)
48
+ bucket.create unless bucket.exists?
49
+ policy = <<-JSON
50
+ {
51
+ "Version": "2012-10-17",
52
+ "Statement": {
53
+ "Resource": "arn:aws:s3:::#{name}/*",
54
+ "Sid": "PaspagonAllow",
55
+ "Effect": "Allow",
56
+ "Principal": {"AWS": "154072225287"},
57
+ "Action": ["s3:GetObject"]
58
+ }
59
+ }
60
+ JSON
61
+ client.put_bucket_policy(bucket: name, policy: policy)
62
+ logging_status =
63
+ {logging_enabled:
64
+ {target_bucket: logging_bucket.name,
65
+ target_prefix: "#{name}/"}}
66
+ client.put_bucket_logging(bucket: name, bucket_logging_status: logging_status)
67
+ end
68
+
69
+ def ensure_logging_bucket(name, client)
70
+ bucket = Aws::S3::Bucket.new(name: name, client: client)
71
+ bucket.create unless bucket.exists?
72
+ lifecycle_configuration =
73
+ {rules:
74
+ [{expiration:
75
+ {days: 90},
76
+ prefix: '',
77
+ status: 'Enabled'
78
+ }]}
79
+ client.put_bucket_lifecycle_configuration(bucket: name, lifecycle_configuration: lifecycle_configuration)
80
+ client.put_bucket_acl(bucket: name, acl: 'log-delivery-write')
81
+ bucket
82
+ end
83
+
84
+ def sync_path(path, client)
85
+ Dir.chdir(path) do
86
+ local_files =
87
+ Dir.glob('**/*').select { |p| File.file?(p) }.map { |p| [p, [File.mtime(p), File.ctime(p)].max] }.to_h
88
+
89
+ bucket_name = File.basename(path)
90
+ bucket = Aws::S3::Bucket.new(bucket_name, client: client)
91
+ cloud_files = bucket.objects.map { |o| [o.key, o.last_modified] }.to_h
92
+
93
+ to_delete = (cloud_files.keys - local_files.keys)
94
+ unless to_delete.empty?
95
+ puts('Deleting objects:')
96
+ to_delete.each do |p|
97
+ puts("\t#{p}")
98
+ end
99
+ bucket.delete_objects(delete: {objects: to_delete.map { |p| {key: p} }})
100
+ end
101
+
102
+ to_upload = local_files.keys.select { |p| !cloud_files[p] || local_files[p] > cloud_files[p] }
103
+ to_upload.each do |p|
104
+ puts("Uploading #{p} to bucket #{bucket_name}…")
105
+ xattr = Xattr.new(p)
106
+ metadata = xattr.as_json.select { |k, _| k.start_with?('user.') }.map { |k, v| [k.sub(/^user./, ''), v] }.to_h
107
+ bucket.put_object(body: File.open(p),
108
+ metadata: metadata,
109
+ key: p)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class PaspagonConfig
4
+ attr_reader :buckets, :formats, :full_config
5
+
6
+ def initialize(site)
7
+ @site = site
8
+ @full_config = site.config['paspagon'] || {}
9
+ @terms_accepted = @full_config['accept-terms'] == 'https://github.com/Paspagon/paspagon.github.io/blob/master/terms-seller.md'
10
+ raise 'Paspagon terms not accepted' unless @terms_accepted
11
+ @buckets = @full_config['buckets'] || {}
12
+ @formats = @full_config['formats'] || {}
13
+ end
14
+
15
+ def post_config_complete(post)
16
+ payment_config = full_payment_config post
17
+ prices(payment_config) && addresses(payment_config)
18
+ end
19
+
20
+ def bucket_name(post)
21
+ bucket = post.data['bucket']
22
+ raise 'Bucket not specified for post' unless bucket
23
+ raise "Bucket not found: #{bucket}" unless @buckets.key?(bucket)
24
+ bucket
25
+ end
26
+
27
+ def format_configs(post)
28
+ @formats.merge(post.data['formats'] || {})
29
+ end
30
+
31
+ def paid_dest_dir
32
+ File.join(@site.dest, '../_paid')
33
+ end
34
+
35
+ def bucket_dest_dir(bucket_name)
36
+ File.join(paid_dest_dir, bucket_name)
37
+ end
38
+
39
+ def write_buckets_config
40
+ @buckets.each do |bucket_name, bucket_hash|
41
+ bucket_config = bucket_hash.update('accept-terms' => @full_config['accept-terms'])
42
+ ini = hash_to_ini bucket_config
43
+ dest = File.join(bucket_dest_dir(bucket_name), 'paspagon.ini')
44
+ ini_hash = Digest::SHA256.digest(ini)
45
+ next if File.exist?(dest) && Digest::SHA256.file(dest).digest == ini_hash
46
+ File.open(dest, 'wb') do |f|
47
+ f.write(ini)
48
+ end
49
+ end
50
+ end
51
+
52
+ def logging_bucket_name
53
+ @full_config['logging_bucket']
54
+ end
55
+ end
56
+
57
+ def hash_to_ini(hash, section = nil, section_nested = nil)
58
+ sections, entries = hash.partition { |_, v| v.is_a? Hash }
59
+ lines = []
60
+ lines += ["[#{section}]"] if section && !section_nested
61
+ section_prefix = section_nested ? "#{section}-" : ''
62
+ lines += entries.map { |e| section_prefix + e.join(' = ') }
63
+ lines += sections.map do |inner_section, inner_section_entries|
64
+ hash_to_ini(inner_section_entries, section_prefix + inner_section, section)
65
+ end
66
+ lines.flatten.join("\n")
67
+ end
@@ -0,0 +1,180 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'ffi-xattr'
5
+ require 'jekyll-paspagon/config'
6
+
7
+ Jekyll::Hooks.register :site, :pre_render do |site|
8
+ config = PaspagonConfig.new(site)
9
+ site.exclude << '_paid' unless site.exclude.include?('_paid')
10
+ site.posts.docs.each do |post|
11
+ post.data['formats'] ||= {}
12
+ # Jekyll does not do a simple merge of default values with post values, so
13
+ # we cannot unset default formats in an ordinary way. Instead, we reject null values.
14
+ post.data['formats'].reject! { |_, v| !v }
15
+ prices = post.data['price'] || {}
16
+ addresses = post.data['address'] || {}
17
+ link_expiration_time = post.data['link-expiration-time'] || nil
18
+ config.format_configs(post).each do |format, format_config|
19
+ paid = format_paid? post, format_config
20
+ format_dest_dir =
21
+ if paid
22
+ bucket_name = config.bucket_name post
23
+ File.dirname(File.join(config.bucket_dest_dir(bucket_name), post.url))
24
+ else
25
+ File.dirname(File.join(site.dest, post.url))
26
+ end
27
+ post_basename = File.basename(post.url)
28
+ format_filename = "#{post_basename}.#{format}"
29
+ format_dest = File.join(format_dest_dir, format_filename)
30
+ format_url_ending =
31
+ if format == 'html' && !paid
32
+ post.url
33
+ else
34
+ File.join(File.dirname(post.url), format_filename)
35
+ end
36
+ format_url =
37
+ if paid
38
+ bucket_name = config.bucket_name post
39
+ "https://s3-us-west-2.paspagon.com/#{bucket_name}#{format_url_ending}"
40
+ else
41
+ "#{site.config['url']}#{site.config['baseurl']}#{format_url_ending}"
42
+ end
43
+
44
+ format_info =
45
+ {'paid' => paid,
46
+ 'post' => post,
47
+ 'path' => format_dest,
48
+ 'full_url' => format_url,
49
+ 'link_expiration_time' => link_expiration_time,
50
+ 'prices' => prices,
51
+ 'name' => format.upcase,
52
+ 'addresses' => addresses}
53
+ post.data['formats'][format].merge! format_info
54
+ post.data['format_array'] = post.data['formats'].map { |_, v| v }
55
+ end
56
+ end
57
+
58
+ config.write_buckets_config
59
+ end
60
+
61
+ Jekyll::Hooks.register :posts, :post_write do |post|
62
+ unless post.data['excerpt_only']
63
+ post.data['formats'].each do |format, format_config|
64
+ puts("Generated #{format_config['path']}.") if maybe_generate_doc(post, format)
65
+ attrs = format_xattrs(format_config)
66
+ sync_payment_attributes(format_config['path'], attrs)
67
+ end
68
+ end
69
+ end
70
+
71
+ def format_xattrs(format_config)
72
+ attrs = {}
73
+ format_config['prices'].each do |currency, price|
74
+ attrs["user.x-amz-meta-price-#{currency}"] = price if price
75
+ end
76
+ format_config['addresses'].each do |currency, address|
77
+ attrs["user.x-amz-meta-address-#{currency}"] = address if address
78
+ end
79
+ attrs['user.x-amz-meta-link-expiration-time'] = format_config['link_expiration_time'] if format_config['link_expiration_time']
80
+ attrs['user.content-disposition'] = format_config['content_disposition'] if format_config['content_disposition']
81
+ attrs['user.content-type'] = format_config['content_type'] if format_config['content_type']
82
+ attrs
83
+ end
84
+
85
+ def sync_payment_attributes(path, attrs)
86
+ xattr = Xattr.new(path)
87
+ old = xattr.list.select { |a| a.start_with?('user.') }
88
+ new = attrs.keys
89
+ (old - new).each do |a|
90
+ xattr.remove a
91
+ end
92
+ new.each do |a|
93
+ xattr[a] = attrs[a] unless xattr[a] == attrs[a].to_s
94
+ end
95
+ end
96
+
97
+ def maybe_generate_doc(post, format)
98
+ format_config = post.data['formats'][format]
99
+ dest_path = format_config['path']
100
+ FileUtils.mkdir_p(File.dirname(dest_path))
101
+ if File.exist?(dest_path)
102
+ source_path = post.path
103
+ if File.ctime(source_path) > File.ctime(dest_path)
104
+ temp_path = File.join(Dir.tmpdir, Digest::SHA256.hexdigest(dest_path) + '.' + format)
105
+ generate_doc(post, format, format_config, temp_path)
106
+ temp_hash = Digest::SHA256.file(temp_path)
107
+ dest_hash = Digest::SHA256.file(dest_path)
108
+ if temp_hash == dest_hash
109
+ # Unfortunately, this check usually doesn’t yield desired effect, as Pandoc’s conversion to EPUB is impure.
110
+ File.delete(temp_path)
111
+ false
112
+ else
113
+ FileUtils.mv(temp_path, dest_path)
114
+ true
115
+ end
116
+ else
117
+ false
118
+ end
119
+ else
120
+ generate_doc(post, format, format_config, dest_path)
121
+ true
122
+ end
123
+ end
124
+
125
+ def generate_doc(post, format, format_config, dest_path)
126
+ case format
127
+ when 'html'
128
+ if format_config['paid']
129
+ free_dest = post.destination
130
+ system("mv #{free_dest} #{dest_path}")
131
+ post.content = post.data['excerpt'].output
132
+ post.data['excerpt_only'] = true
133
+ post.output = Jekyll::Renderer.new(post.site, post, post.site.site_payload).run
134
+ # We don’t use post.write method to not trigger infinite hook recursion.
135
+ File.open(free_dest, 'wb') do |f|
136
+ f.write(post.output)
137
+ end
138
+ end # If HTML format is free, then it is already generated, so we don’t need to do anything.
139
+ else
140
+ command = generation_command(post, format, format_config, dest_path)
141
+ raise "Failed to execute: #{command}" unless system command
142
+ end
143
+ end
144
+
145
+ def generation_command(post, format, _format_config, dest_path)
146
+ input_format = pandoc_input_format(post.site)
147
+ case format
148
+ when 'azw3', 'mobi', 'pdf'
149
+ intermediary_path = File.join(Dir.tmpdir, Digest::SHA256.hexdigest(dest_path) + '.epub')
150
+ "pandoc #{post.path} -f #{input_format} -t epub -o #{intermediary_path} && ebook-convert #{intermediary_path} #{dest_path} > /dev/null && rm #{intermediary_path}"
151
+ else
152
+ "pandoc #{post.path} -f #{input_format} -t #{format} -o #{dest_path}"
153
+ end
154
+ end
155
+
156
+ def pandoc_input_format(site)
157
+ (site.config['pandoc'] || {})['input'] ||
158
+ begin
159
+ kramdown = site.config['kramdown'] || {}
160
+ format = 'markdown_github'
161
+ format += '-hard_line_breaks' unless kramdown['hard_wrap']
162
+ format
163
+ end
164
+ end
165
+
166
+ def format_paid?(post, format_config)
167
+ too_late =
168
+ if format_config['paid_before']
169
+ Date.today - Date.parse(post.date.to_s) >= format_config['paid_before'].to_i
170
+ else
171
+ false
172
+ end
173
+ too_soon =
174
+ if format_config['paid_after']
175
+ Date.today - Date.parse(post.date.to_s) <= format_config['paid_after'].to_i
176
+ else
177
+ false
178
+ end
179
+ !too_late && !too_soon
180
+ end
@@ -0,0 +1,3 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'jekyll-paspagon/hooks'
3
+ require 'jekyll/commands/paspagon'
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-paspagon
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Krzysztof Jurewicz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi-xattr
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.1.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: aws-sdk
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rubocop
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.38'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 0.38.0
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '0.38'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 0.38.0
67
+ description: Sell your Jekyll posts in various formats for cryptocurrencies
68
+ email: krzysztof.jurewicz@gmail.com
69
+ executables: []
70
+ extensions: []
71
+ extra_rdoc_files:
72
+ - README.md
73
+ - LICENSE
74
+ files:
75
+ - LICENSE
76
+ - README.md
77
+ - lib/jekyll-paspagon.rb
78
+ - lib/jekyll-paspagon/config.rb
79
+ - lib/jekyll-paspagon/hooks.rb
80
+ - lib/jekyll/commands/paspagon.rb
81
+ homepage: https://github.com/KrzysiekJ/jekyll-paspagon
82
+ licenses:
83
+ - MIT
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.2.2
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Sell your Jekyll posts
105
+ test_files: []
106
+ has_rdoc: