slate_algolia 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e80c2ccf5f876da5001c305e6d272a21287e313d
4
- data.tar.gz: 8e96a63ef046eef75dc8c32dbccf3e837acf892d
3
+ metadata.gz: d7501fd269fa65aa278b4e78674e7fcb2236ac63
4
+ data.tar.gz: b181aa3c1968582c0a5b817cd5ee9c99bb042b57
5
5
  SHA512:
6
- metadata.gz: 87591952fd37a850f76772d4a4935210cbcef20f7a38f3015f8fd187b6ebc78ee83170cd80a9bd77d84b13ee41db0a5cf7cbbd6e7a49401652069bd01d6092f6
7
- data.tar.gz: f5ef2a9aeb23290ab53e4b03cfc9cb8f1fb49b4a9bcc68ecda146c294c4863f342e1115d99043313dcdbb6b0065c78ef93559947b89327136597de2335be653e
6
+ metadata.gz: d05ec3b8c68eec7f1ec8bf1c1fb22665374bbcafaa364feaa93fd9a2e5da07af5dd304a936faf92317bad3c1f1e393b934c6880811138a8dde3b0ef99d48b20f
7
+ data.tar.gz: b79afe1e0dbf732f73fdb56c307317170b3bb3c00619ae2acaddc2bc3ab0cf714d930346d3474086199c9931be21c655a29e95bd259930ebab983dea92565e43
@@ -0,0 +1,9 @@
1
+ ## What does this PR do?
2
+
3
+ ## How should this be tested?
4
+
5
+ Step through the code line by line. Things to keep in mind as you review:
6
+ - Are there any edge cases not covered by this code?
7
+ - Does this code follow conventions (naming, formatting, modularization, etc) where applicable?
8
+
9
+ ## Related tickets?
data/.gitignore CHANGED
@@ -2,4 +2,5 @@
2
2
  /Gemfile.lock
3
3
 
4
4
  # Ignore pkg folder
5
- /pkg
5
+ /pkg
6
+ /tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ Style/BracesAroundHashParameters:
2
+ EnforcedStyle: context_dependent
3
+
4
+ LineLength:
5
+ Max: 120
data/CONTRIBUTING.md CHANGED
@@ -29,6 +29,15 @@ If a maintainer asks you to "rebase" your PR, they're saying that a lot of code
29
29
  ```ssh
30
30
  $ git clone https://github.com/keen/slate_algolia.git && cd slate_algolia
31
31
  $ bower install
32
+
33
+ # Run the specs
34
+ bundle exec rake spec
35
+
36
+ # Run the style guide
37
+ bundle exec rake style
38
+
39
+ # Run the full test suite
40
+ bundle exec rake
32
41
  ```
33
42
 
34
43
  ### Submitting a Pull Request
data/Gemfile CHANGED
@@ -12,7 +12,8 @@ group :development do
12
12
  end
13
13
 
14
14
  group :test do
15
- gem 'cucumber'
16
- gem 'aruba'
17
15
  gem 'rspec'
16
+ gem 'middleman'
17
+ gem 'webmock'
18
+ gem 'rubocop', require: false
18
19
  end
data/README.md CHANGED
@@ -4,7 +4,26 @@
4
4
 
5
5
  ## Installation
6
6
 
7
- The most simple way to activate this is to add this code to your `config.rb`:
7
+ If you're not using Bundler, simply install the gem:
8
+
9
+ ```ssh
10
+ gem install slate_algolia
11
+ ```
12
+
13
+ If you are using Bundler, add `slate_algolia` to your Gemfile
14
+ ```ruby
15
+ gem slate_algolia
16
+ ```
17
+
18
+ and then reinstall your gems
19
+
20
+ ```ssh
21
+ bundle install
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ The most simple way to activate the extension is to add this code to your `config.rb`:
8
27
 
9
28
  ```ruby
10
29
  activate :slate_algolia do |options|
@@ -19,8 +38,6 @@ You also need to add a line to the YAML Frontmatter of your Slate docs index. Th
19
38
  algolia_search: true
20
39
  ```
21
40
 
22
- ## Configuration
23
-
24
41
  There are some additional configurations you can enable:
25
42
 
26
43
  ```ruby
@@ -29,9 +46,48 @@ activate :slate_algolia do |options|
29
46
  options.api_key = '1234' # Algolia API Key
30
47
  options.dry_run = true # Don't send data to Algolia, but output some log information instead
31
48
  options.parsers = {} # Custom tag parsers (discussed later in the docs)
49
+ options.before_index = nil # Proc for changing the data model before it is sent to Algolia
50
+ end
51
+ ```
52
+
53
+ ## Changing the Data Model
54
+
55
+ While the data model built in is pretty well thought-out, everyone's search needs will be different. Some projects of course will need to mold the data model to meet their needs. To do that, you can hook in to the indexing process and modify the records _just before_ they are shipped off to Algolia.
56
+
57
+ Set it up in your config file:
58
+
59
+ ```ruby
60
+ activate :slate_algolia do |options|
61
+ options.before_index = proc { |record|
62
+ # Change the key name for the body to 'content'
63
+ record[:content] = record[:body]
64
+ record.delete(:body)
65
+
66
+ record
67
+ }
32
68
  end
33
69
  ```
34
70
 
71
+ If you would like to turn a single record into multiple records, simply return an array of records
72
+
73
+ ```ruby
74
+ activate :slate_algolia do |options|
75
+ options.before_index = proc { |record|
76
+ # Create a record for each language in the code examples
77
+ record.permanent_code.map.with_index { |language, code|
78
+ new_record = record.merge({
79
+ code: code,
80
+ language: language
81
+ })
82
+ new_record.delete(permanent_code)
83
+
84
+ new_record
85
+ }
86
+ }
87
+ end
88
+ ```
89
+
90
+
35
91
  ## Custom Tag Parser
36
92
 
37
- TODO
93
+ TODO
data/Rakefile CHANGED
@@ -1,14 +1,7 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
1
+ require 'rspec/core/rake_task'
2
+ require 'rubocop/rake_task'
3
3
 
4
- require 'cucumber/rake/task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ RuboCop::RakeTask.new(:style)
5
6
 
6
- Cucumber::Rake::Task.new(:cucumber, 'Run features that should pass') do |t|
7
- t.cucumber_opts = "--color --tags ~@wip --strict"
8
- end
9
-
10
- require 'rake/clean'
11
-
12
- task test: ['cucumber']
13
-
14
- task default: :test
7
+ task default: [:spec, :style]
@@ -3,77 +3,102 @@ require 'slate_algolia/parser'
3
3
  require 'slate_algolia/index'
4
4
 
5
5
  module Middleman
6
- module SlateAlgolia
7
- class Extension < Middleman::Extension
8
- option :parsers, {}, 'Custom tag parsers'
9
- option :dry_run, false, 'Send data to Algolia or not?'
10
- option :application_id, '', 'Algolia Application ID'
11
- option :api_key, '', 'Algolia API Key'
12
-
13
- def initialize(app, options_hash={}, &block)
14
- super
15
-
16
- tag_parsers = set_parser_defaults(options.parsers)
17
- dry_run = options.dry_run
18
- application_id = options.application_id
19
- api_key = options.api_key
20
-
21
- app.after_build do |builder|
22
- sitemap.where(:algolia_search.equal => true).all.each do |slate_page|
23
- content_parser = Parser.new(slate_page, tag_parsers)
24
-
25
- if content_parser.sections.length > 0
26
- index = Index.new(application_id, api_key, dry_run)
27
-
28
- content_parser.sections.each do |section|
29
- index.queue_object(section)
30
- end
31
-
32
- index.flush_queue
33
- index.clean_index
34
- end
35
- end
36
- end
6
+ module SlateAlgolia
7
+ # Base extension orchestration
8
+ class Extension < Middleman::Extension
9
+ option :parsers, {}, 'Custom tag parsers'
10
+ option :dry_run, false, 'Send data to Algolia or not?'
11
+ option :application_id, '', 'Algolia Application ID'
12
+ option :index_name, 'API Docs', 'Name for the Algolia Index'
13
+ option :api_key, '', 'Algolia API Key'
14
+ option :before_index, nil, 'A block to run on each record before it is sent to the index'
15
+
16
+ def initialize(app, options_hash = {}, &block)
17
+ super
18
+ merge_parser_defaults(options.parsers)
19
+ end
20
+
21
+ def after_build
22
+ parse_content
23
+ index.flush_queue
24
+ index.clean_index
25
+ end
26
+
27
+ def index
28
+ @index ||= Index.new(
29
+ application_id: options.application_id,
30
+ api_key: options.api_key,
31
+ name: options.index_name,
32
+ dry_run: options.dry_run,
33
+ before_index: options.before_index
34
+ )
35
+ end
36
+
37
+ # rubocop:disable AbcSize, MethodLength
38
+ def parsers
39
+ @parsers ||= {
40
+ pre: lambda do |node, section, page|
41
+ languages = node.get('class').split
42
+ languages.delete('highlight')
43
+
44
+ if languages.length
45
+
46
+ # if the current language is in the list of language tabs
47
+ code_type = if page.metadata[:page]['language_tabs'].include?(languages.first)
48
+ :tabbed_code
49
+ else
50
+ :permanent_code
51
+ end
52
+
53
+ section[code_type] = {} unless section[code_type]
54
+
55
+ section[code_type][languages.first.to_sym] = node.text
37
56
  end
38
57
 
39
- private
40
-
41
- def parser_defaults
42
- {
43
- pre: -> (node, section, page) do
44
- languages = node.get('class').split
45
- languages.delete('highlight')
46
-
47
- if languages.length
48
-
49
- # if the current language is in the list of language tabs
50
- if page.metadata[:page]['language_tabs'].include?(languages.first)
51
- code_type = :tabbed_code
52
- else
53
- code_type = :permanent_code
54
- end
55
-
56
- unless section[code_type]
57
- section[code_type] = {}
58
- end
59
-
60
- section[code_type][languages.first.to_sym] = node.text
61
- end
62
- end,
63
-
64
- blockquote: -> (node, section, page) do
65
- unless section[:annotations]
66
- section[:annotations] = []
67
- end
68
-
69
- section[:annotations].push(node.text)
70
- end
71
- }
72
- end
73
-
74
- def set_parser_defaults(custom_parsers)
75
- parser_defaults.merge(custom_parsers)
76
- end
58
+ section
59
+ end,
60
+
61
+ blockquote: lambda do |node, section, _page|
62
+ section[:annotations] = [] unless section[:annotations]
63
+ section[:annotations].push(node.text)
64
+ section
65
+ end,
66
+
67
+ h1: lambda do |node, _section, _page|
68
+ {
69
+ objectID: node.get('id'),
70
+ title: node.text,
71
+ body: ''
72
+ }
73
+ end,
74
+
75
+ h2: lambda do |node, _section, _page|
76
+ {
77
+ objectID: node.get('id'),
78
+ title: node.text,
79
+ body: ''
80
+ }
81
+ end
82
+ }
83
+ end
84
+ # rubocop:enable AbcSize, MethodLength
85
+
86
+ private
87
+
88
+ def parse_content
89
+ app.sitemap.where(:algolia_search.equal => true).all.each do |slate_page|
90
+ content_parser = Parser.new(slate_page, parsers)
91
+ next if content_parser.sections.empty?
92
+
93
+ content_parser.sections.each do |section|
94
+ index.queue_object(section)
95
+ end
77
96
  end
97
+ end
98
+
99
+ def merge_parser_defaults(custom_parsers)
100
+ parsers.merge(custom_parsers)
101
+ end
78
102
  end
103
+ end
79
104
  end
@@ -1,51 +1,68 @@
1
1
  require 'algoliasearch'
2
2
 
3
3
  module Middleman
4
- module SlateAlgolia
5
- class Index
6
-
7
- def initialize(app_id, api_key, dry_run = false)
8
- @publish = !dry_run
9
- @published = []
10
-
11
- Algolia.init :application_id => app_id,
12
- :api_key => api_key
13
-
14
- @index = Algolia::Index.new("API Docs")
15
- @queue = []
16
- end
17
-
18
- def queue_object(data)
19
- @queue.push(data)
20
-
21
- if @queue.length >= 1000
22
- flush_queue()
23
- end
24
- end
25
-
26
- def clean_index
27
- old_content = @index.browse['hits'].reject { |hit|
28
- @published.any? { |entry| entry[:id] == hit['id'] }
29
- }
30
-
31
- if @publish
32
- @index.delete_objects(old_content.map { |hit| hit['objectID'] })
33
- else
34
- puts "would have deleted #{old_content.size} items if not in dry mode"
35
- end
36
- end
37
-
38
- def flush_queue
39
- to_publish = @queue.reject { |obj| obj[:id].nil? }
40
- if @publish
41
- @index.add_objects(to_publish)
42
- else
43
- puts "would have published #{to_publish.size} items if not in dry mode"
44
- end
45
-
46
- @published.concat(to_publish)
47
- @queue = []
48
- end
4
+ module SlateAlgolia
5
+ # Manages the Index stored in Algolia
6
+ class Index
7
+ def initialize(options)
8
+ @options = options
9
+ @publish = !@options[:dry_run]
10
+ @published = []
11
+
12
+ Algolia.init application_id: @options[:application_id],
13
+ api_key: @options[:api_key]
14
+
15
+ @index = Algolia::Index.new(options[:name])
16
+ @queue = []
17
+ end
18
+
19
+ def queue_object(data)
20
+ @queue.push(data)
21
+
22
+ flush_queue if @queue.length >= 1000
23
+ end
24
+
25
+ def clean_index
26
+ old_content = @index.browse['hits'].reject do |hit|
27
+ @published.any? { |entry| entry[:id] == hit['id'] }
28
+ end
29
+
30
+ if @publish
31
+ @index.delete_objects(old_content.map { |hit| hit['objectID'] })
32
+ else
33
+ puts "would have deleted #{old_content.size} items if not in dry mode"
34
+ end
35
+ end
36
+
37
+ def run_before_index
38
+ return @queue unless @options[:before_index].instance_of?(Proc)
39
+
40
+ @queue = @queue.map do |record|
41
+ new_record = @options[:before_index].call(record)
42
+ if new_record
43
+ new_record
44
+ else
45
+ record
46
+ end
47
+ end
48
+
49
+ @queue.flatten!
50
+ end
51
+
52
+ def flush_queue
53
+ run_before_index
54
+
55
+ to_publish = @queue.reject { |obj| obj[:objectID].nil? }
56
+
57
+ if @publish
58
+ @index.add_objects(to_publish)
59
+ else
60
+ puts "would have published #{to_publish.size} items if not in dry mode"
49
61
  end
62
+
63
+ @published.concat(to_publish)
64
+ @queue = []
65
+ end
50
66
  end
67
+ end
51
68
  end
@@ -1,58 +1,79 @@
1
1
  require 'oga'
2
2
 
3
3
  module Middleman
4
- module SlateAlgolia
5
- class Parser
6
-
7
- def initialize(middleman_page, parsers)
8
- @middleman_page = middleman_page
9
- @page = Oga.parse_html(middleman_page.render({}, {current_page: middleman_page }))
10
- @parsers = parsers
11
- get_content
12
- generate_sections
13
- end
14
-
15
- def sections
16
- @sections ||= []
17
- end
18
-
19
- private
20
-
21
- def get_content
22
- @content = @page.css('.content').first
23
- end
24
-
25
- def generate_sections
26
- @sections = []
27
- current_section = {title: 'introduction-header'}
28
-
29
- @content.children.each do |node|
30
- next unless node.class == Oga::XML::Element
31
-
32
- if node.name == 'h1' or node.name == 'h2'
33
- @sections.push(current_section) unless current_section[:title].nil? and current_section[:body].empty?
34
- current_section = {
35
- id: node.get('id'),
36
- title: node.text
37
- }
38
- else
39
- parser = @parsers[node.name.to_sym] || self.method(:default_tag_parser)
40
- parser.call(node, current_section, @middleman_page)
41
- end
42
- end
43
-
44
- @sections.push(current_section)
45
- end
46
-
47
- def default_tag_parser(node, section, page)
48
- if section[:body]
49
- section[:body] += "\n"
50
- else
51
- section[:body] = ""
52
- end
53
-
54
- section[:body] += node.text
55
- end
4
+ module SlateAlgolia
5
+ # Parses the HTML and translates it to an Index Record format
6
+ class Parser
7
+ def initialize(middleman_page, parsers)
8
+ @middleman_page = middleman_page
9
+ @page = Oga.parse_html(
10
+ middleman_page.render(
11
+ {},
12
+ { current_page: middleman_page }
13
+ )
14
+ )
15
+ @parsers = parsers
16
+ generate_sections
17
+ end
18
+
19
+ def sections
20
+ @sections ||= []
21
+ end
22
+
23
+ private
24
+
25
+ def content
26
+ @content ||= @page.css('.content').first
27
+ end
28
+
29
+ def generate_sections
30
+ current_section = {
31
+ objectID: 'intro',
32
+ title: 'introduction-header',
33
+ body: ''
34
+ }
35
+
36
+ content.children.each do |node|
37
+ next unless node.class == Oga::XML::Element
38
+ current_section = parse_node(node, current_section, @middleman_page)
39
+ end
40
+
41
+ sections.push(current_section)
42
+ end
43
+
44
+ def handle_new_section(node, current_section)
45
+ if node.name == 'h1' || node.name == 'h2'
46
+ unless current_section[:title].nil? &&
47
+ current_section[:body].empty?
48
+
49
+ sections.push(current_section)
50
+ end
51
+
52
+ {}
53
+ else
54
+ current_section
55
+ end
56
+ end
57
+
58
+ def parse_node(node, section, page)
59
+ section = handle_new_section(node, section)
60
+ parser = @parsers[node.name.to_sym] ||
61
+ method(:default_tag_parser)
62
+
63
+ parser.call(node, section, page)
64
+ end
65
+
66
+ def default_tag_parser(node, section, _page)
67
+ if section[:body]
68
+ section[:body] += '\n'
69
+ else
70
+ section[:body] = ''
56
71
  end
72
+
73
+ section[:body] += node.text
74
+
75
+ section
76
+ end
57
77
  end
78
+ end
58
79
  end
data/lib/slate_algolia.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'middleman-core'
2
+ require 'slate_algolia/index'
3
+ require 'slate_algolia/parser'
4
+ require 'slate_algolia/extension'
2
5
 
3
6
  ::Middleman::Extensions.register(:slate_algolia) do
4
- require 'slate_algolia/extension'
5
- ::Middleman::SlateAlgolia::Extension
7
+ ::Middleman::SlateAlgolia::Extension
6
8
  end
@@ -1,21 +1,20 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.name = "slate_algolia"
6
- s.version = "0.0.1"
5
+ s.name = 'slate_algolia'
6
+ s.version = '1.0.0'
7
7
  s.platform = Gem::Platform::RUBY
8
- s.authors = ["Joe Wegner"]
9
- s.email = ["joe@wegnerdesign.com"]
10
- s.summary = "Quickly and easily index Slate Docs in Algolia"
8
+ s.authors = ['Joe Wegner']
9
+ s.email = ['joe@wegnerdesign.com']
10
+ s.summary = 'Quickly and easily index Slate Docs in Algolia'
11
11
 
12
12
  s.files = `git ls-files`.split("\n")
13
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
- s.require_paths = ["lib"]
16
-
13
+ s.test_files = `git ls-files -- spec/*`.split("\n")
14
+ s.require_paths = ['lib']
15
+
17
16
  # The version of middleman-core your extension depends on
18
- s.add_runtime_dependency("middleman-core", ["~> 3.3", ">= 3.3.12"])
19
- s.add_runtime_dependency("oga", ["~> 1.3", ">= 1.3.1"])
20
- s.add_runtime_dependency("algoliasearch", ["~> 1.6", ">= 1.6.1"])
17
+ s.add_runtime_dependency('middleman-core', ['~> 3.3', '>= 3.3.12'])
18
+ s.add_runtime_dependency('oga', ['~> 1.3', '>= 1.3.1'])
19
+ s.add_runtime_dependency('algoliasearch', ['~> 1.6', '>= 1.6.1'])
21
20
  end