dropdown 0.8.1
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 +15 -0
- data/.gitignore +23 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +194 -0
- data/Rakefile +41 -0
- data/dropdown.gemspec +37 -0
- data/lib/dropdown.rb +23 -0
- data/lib/dropdown/blog.rb +23 -0
- data/lib/dropdown/configuration.rb +12 -0
- data/lib/dropdown/constants.rb +4 -0
- data/lib/dropdown/dropbox.rb +1 -0
- data/lib/dropdown/dropbox/session.rb +18 -0
- data/lib/dropdown/errors.rb +4 -0
- data/lib/dropdown/inflector.rb +11 -0
- data/lib/dropdown/iterators.rb +3 -0
- data/lib/dropdown/iterators/dropbox_iterator.rb +31 -0
- data/lib/dropdown/iterators/file_iterator.rb +18 -0
- data/lib/dropdown/iterators/iterator_factory.rb +12 -0
- data/lib/dropdown/markdown_renderer.rb +21 -0
- data/lib/dropdown/output_stores.rb +3 -0
- data/lib/dropdown/output_stores/dropbox_store.rb +24 -0
- data/lib/dropdown/output_stores/file_store.rb +23 -0
- data/lib/dropdown/output_stores/output_store_factory.rb +12 -0
- data/lib/dropdown/parsers/excerpt_extractor.rb +38 -0
- data/lib/dropdown/parsers/metadata_parser.rb +30 -0
- data/lib/dropdown/parsers/parser.rb +12 -0
- data/lib/dropdown/post.rb +48 -0
- data/lib/dropdown/processor.rb +39 -0
- data/lib/dropdown/readers.rb +3 -0
- data/lib/dropdown/readers/dropbox_reader.rb +32 -0
- data/lib/dropdown/readers/file_reader.rb +13 -0
- data/lib/dropdown/readers/reader_factory.rb +12 -0
- data/lib/dropdown/renderer_factory.rb +10 -0
- data/lib/dropdown/version.rb +3 -0
- data/spec/dropdown/blog_spec.rb +62 -0
- data/spec/dropdown/configuration_spec.rb +12 -0
- data/spec/dropdown/dropbox/session_spec.rb +26 -0
- data/spec/dropdown/inflector_spec.rb +18 -0
- data/spec/dropdown/iterators/dropbox_iterator_spec.rb +53 -0
- data/spec/dropdown/iterators/file_iterator_spec.rb +43 -0
- data/spec/dropdown/iterators/iterator_factory_spec.rb +10 -0
- data/spec/dropdown/markdown_renderer_spec.rb +31 -0
- data/spec/dropdown/output_stores/dropbox_store_spec.rb +46 -0
- data/spec/dropdown/output_stores/file_store_spec.rb +31 -0
- data/spec/dropdown/output_stores/output_store_factory_spec.rb +9 -0
- data/spec/dropdown/parsers/excerpt_extractor_spec.rb +54 -0
- data/spec/dropdown/parsers/metadata_parser_spec.rb +35 -0
- data/spec/dropdown/parsers/parser_spec.rb +26 -0
- data/spec/dropdown/post_spec.rb +66 -0
- data/spec/dropdown/processor_spec.rb +80 -0
- data/spec/dropdown/readers/dropbox_reader_spec.rb +43 -0
- data/spec/dropdown/readers/file_reader_spec.rb +30 -0
- data/spec/dropdown/readers/reader_factory_spec.rb +9 -0
- data/spec/dropdown/renderer_factory_spec.rb +18 -0
- data/spec/dropdown_spec.rb +13 -0
- data/spec/features/processing_a_markdown_directory_spec.rb +60 -0
- data/spec/fixtures/blog/my-trip-to-africa.md +10 -0
- data/spec/fixtures/processed/my-trip-to-africa.html +16 -0
- data/spec/fixtures/sample_post.md +8 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/dummy_dropbox.rb +63 -0
- metadata +278 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'dropbox_sdk'
|
2
|
+
require_relative '../constants'
|
3
|
+
|
4
|
+
module Dropdown
|
5
|
+
module Iterators
|
6
|
+
class DropboxIterator
|
7
|
+
def initialize(source)
|
8
|
+
raise ArgumentError if source.nil?
|
9
|
+
@source = source.sub(/(\/)+$/, '')
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
contents = client.metadata(@source)['contents']
|
14
|
+
contents.each do |child|
|
15
|
+
unless child['is_dir']
|
16
|
+
path = child['path']
|
17
|
+
if Dropdown::MARKDOWN_EXTENSIONS.include? File.extname(path)
|
18
|
+
yield path
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def client
|
27
|
+
@client ||= DropboxClient.new(Dropdown.configuration.dropbox_access_token)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative '../constants'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
module Iterators
|
5
|
+
class FileIterator
|
6
|
+
def initialize(source)
|
7
|
+
raise ArgumentError if source.nil?
|
8
|
+
@source = source.sub(/(\/)+$/, '')
|
9
|
+
end
|
10
|
+
|
11
|
+
def each
|
12
|
+
Dir.glob("#{@source}/**/*{#{Dropdown::MARKDOWN_EXTENSIONS.join(',')}}") do |markdown_file|
|
13
|
+
yield markdown_file
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative '../inflector'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
module Iterators
|
5
|
+
class IteratorFactory
|
6
|
+
def self.create(iterator, *args)
|
7
|
+
constant_name = Inflector.new("#{iterator}_iterator").constant_name
|
8
|
+
Dropdown::Iterators.const_get(constant_name).new *args
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'redcarpet'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
class MarkdownRenderer
|
5
|
+
attr_reader :file_path
|
6
|
+
attr_accessor :reader
|
7
|
+
|
8
|
+
def initialize(file_path)
|
9
|
+
@file_path = file_path
|
10
|
+
@markdown = Redcarpet::Markdown.new Redcarpet::Render::HTML
|
11
|
+
end
|
12
|
+
|
13
|
+
def render
|
14
|
+
@markdown.render reader.read(file_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def output_filename
|
18
|
+
File.basename(file_path, '.md') + '.html'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'dropbox_sdk'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
module OutputStores
|
5
|
+
class DropboxStore
|
6
|
+
attr_reader :path
|
7
|
+
|
8
|
+
def initialize(output_path)
|
9
|
+
@output_path = output_path
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(contents, file_name)
|
13
|
+
@path = File.join @output_path, file_name
|
14
|
+
client.put_file(@path, contents, true)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def client
|
20
|
+
@client ||= DropboxClient.new(Dropdown.configuration.dropbox_access_token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Dropdown
|
2
|
+
module OutputStores
|
3
|
+
class FileStore
|
4
|
+
attr_reader :path
|
5
|
+
|
6
|
+
def initialize(output_path)
|
7
|
+
@output_path = output_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def save(contents, file_name)
|
11
|
+
create_directory @output_path
|
12
|
+
@path = "#{@output_path}/#{file_name}"
|
13
|
+
File.open(@path, 'w+') { |f| f.write contents }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def create_directory(path)
|
19
|
+
FileUtils.mkdir_p(path)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative '../inflector'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
module OutputStores
|
5
|
+
class OutputStoreFactory
|
6
|
+
def self.create(store, *args)
|
7
|
+
constant_name = Inflector.new("#{store}_store").constant_name
|
8
|
+
Dropdown::OutputStores.const_get(constant_name).new *args
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
module Parsers
|
5
|
+
class ExcerptExtractor
|
6
|
+
include Parser
|
7
|
+
|
8
|
+
def extract
|
9
|
+
@results = ''
|
10
|
+
doc = Nokogiri::HTML.parse(@content)
|
11
|
+
process_element doc
|
12
|
+
@results.chomp
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def process_element(element)
|
18
|
+
if element.text?
|
19
|
+
@results << element.text.gsub(/\n/, '')
|
20
|
+
else
|
21
|
+
unless element.description.nil?
|
22
|
+
# save the seperator for later
|
23
|
+
seperator = element.description.block? ? "\n" : ""
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
element.children.map { |el| process_element(el) }
|
28
|
+
|
29
|
+
# Display a seperator if we are done processing the children
|
30
|
+
if !element.children.empty? &&
|
31
|
+
!seperator.nil? &&
|
32
|
+
!['html', 'body'].include?(element.name)
|
33
|
+
@results << seperator unless seperator.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
module Parsers
|
5
|
+
|
6
|
+
class MetadataParser
|
7
|
+
include Parser
|
8
|
+
|
9
|
+
attr_accessor :headers
|
10
|
+
|
11
|
+
def initialize(content)
|
12
|
+
super
|
13
|
+
@headers = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
doc = Nokogiri::HTML.parse(@content)
|
18
|
+
doc.xpath('//comment()').each { |comment| extract_header(comment.content) }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def extract_header(line)
|
24
|
+
pair = line.split(/:(.+)?/).map(&:strip)
|
25
|
+
@headers[pair[0].downcase.to_sym] = pair[1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Dropdown
|
2
|
+
class Post
|
3
|
+
attr_reader :source, :reader
|
4
|
+
|
5
|
+
def title
|
6
|
+
metadata[:title]
|
7
|
+
end
|
8
|
+
|
9
|
+
def author
|
10
|
+
metadata[:author]
|
11
|
+
end
|
12
|
+
|
13
|
+
def date
|
14
|
+
Date.parse metadata[:date]
|
15
|
+
end
|
16
|
+
|
17
|
+
def body
|
18
|
+
content
|
19
|
+
end
|
20
|
+
|
21
|
+
def excerpt
|
22
|
+
unless @excerpt
|
23
|
+
extractor = Dropdown::Parsers::ExcerptExtractor.new(content)
|
24
|
+
@excerpt = extractor.extract
|
25
|
+
end
|
26
|
+
@excerpt
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(source, reader)
|
30
|
+
@source = source
|
31
|
+
@reader = reader
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def metadata
|
37
|
+
unless @metadata
|
38
|
+
@metadata = Dropdown::Parsers::MetadataParser.new(content)
|
39
|
+
@metadata.parse
|
40
|
+
end
|
41
|
+
@metadata.headers
|
42
|
+
end
|
43
|
+
|
44
|
+
def content
|
45
|
+
@content ||= reader.read(@source)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'renderer_factory'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
class Processor
|
5
|
+
attr_accessor :source,
|
6
|
+
:destination,
|
7
|
+
:storage,
|
8
|
+
:reader,
|
9
|
+
:markdown_iterator,
|
10
|
+
:output_store,
|
11
|
+
:renderer
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@renderer = Dropdown.configuration.renderer
|
15
|
+
end
|
16
|
+
|
17
|
+
def storage=(value)
|
18
|
+
@storage = value
|
19
|
+
self.reader = Dropdown::Readers::ReaderFactory.create(value)
|
20
|
+
self.markdown_iterator = Dropdown::Iterators::IteratorFactory.create(value, self.source)
|
21
|
+
self.output_store = Dropdown::OutputStores::OutputStoreFactory.create(value, self.destination)
|
22
|
+
end
|
23
|
+
|
24
|
+
def process
|
25
|
+
markdown_iterator.each do |file|
|
26
|
+
renderer = renderer_for file
|
27
|
+
output_store.save renderer.render, renderer.output_filename
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def renderer_for(file)
|
34
|
+
r = RendererFactory.create(renderer, file)
|
35
|
+
r.reader = reader
|
36
|
+
r
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'dropbox_sdk'
|
2
|
+
require_relative '../constants'
|
3
|
+
|
4
|
+
module Dropdown
|
5
|
+
module Readers
|
6
|
+
class DropboxReader
|
7
|
+
def read(path)
|
8
|
+
client.get_file(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_html_files(path)
|
12
|
+
files = []
|
13
|
+
contents = client.metadata(path)['contents']
|
14
|
+
contents.each do |child|
|
15
|
+
unless child['is_dir']
|
16
|
+
path = child['path']
|
17
|
+
if Dropdown::HTML_EXTENSIONS.include? File.extname(path)
|
18
|
+
files << path
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
files
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def client
|
28
|
+
@client ||= DropboxClient.new(Dropdown.configuration.dropbox_access_token)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative '../inflector'
|
2
|
+
|
3
|
+
module Dropdown
|
4
|
+
module Readers
|
5
|
+
class ReaderFactory
|
6
|
+
def self.create(reader, *args)
|
7
|
+
constant_name = Inflector.new("#{reader}_reader").constant_name
|
8
|
+
Dropdown::Readers.const_get(constant_name).new *args
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../support/dummy_dropbox'
|
3
|
+
require_relative '../../lib/dropdown'
|
4
|
+
|
5
|
+
describe Dropdown::Blog do
|
6
|
+
it 'can be initialized with a source directory' do
|
7
|
+
directory = 'foo'
|
8
|
+
blog = Dropdown::Blog.new directory
|
9
|
+
blog.source.should == directory
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#name' do
|
13
|
+
it 'can be set from an attribribute' do
|
14
|
+
blog = Dropdown::Blog.new
|
15
|
+
blog.name = 'Personal'
|
16
|
+
blog.name.should == 'Personal'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'in a local directory' do
|
21
|
+
describe '#posts' do
|
22
|
+
let(:current_directory) { File.expand_path File.dirname(__FILE__) }
|
23
|
+
let(:source_directory) { File.join current_directory, '../fixtures/processed' }
|
24
|
+
|
25
|
+
it 'is initialized to empty' do
|
26
|
+
Dropdown::Blog.new.posts.should be_empty
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'creates a post for each file in the source' do
|
30
|
+
html_files = Dir[File.join(source_directory, '**', '*')]
|
31
|
+
blog = Dropdown::Blog.new source_directory
|
32
|
+
blog.posts.length.should == html_files.length
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'in a Dropbox directory' do
|
38
|
+
include DummyDropbox
|
39
|
+
|
40
|
+
describe '#posts' do
|
41
|
+
let(:access_token) { 'blah' }
|
42
|
+
let(:path) { 'blog' }
|
43
|
+
let(:reader) { Dropdown::Readers::DropboxReader.new }
|
44
|
+
let(:contents) do
|
45
|
+
[{id_dir: false, path: "#{path}/file1.html"},
|
46
|
+
{id_dir: false, path: "#{path}/file1.txt"},
|
47
|
+
{id_dir: false, path: "#{path}/file2.html"}]
|
48
|
+
end
|
49
|
+
|
50
|
+
before do
|
51
|
+
Dropdown.configure { |c| c.dropbox_access_token = access_token }
|
52
|
+
stub_dropbox_metadata access_token, path, contents
|
53
|
+
end
|
54
|
+
after { remove_stubbed_dropbox_files }
|
55
|
+
|
56
|
+
it 'returns all of the html files within a directory' do
|
57
|
+
blog = Dropdown::Blog.new path, reader
|
58
|
+
blog.posts.length.should == 2
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|