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 +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +9 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +5 -0
- data/CONTRIBUTING.md +9 -0
- data/Gemfile +3 -2
- data/README.md +60 -4
- data/Rakefile +5 -12
- data/lib/slate_algolia/extension.rb +94 -69
- data/lib/slate_algolia/index.rb +62 -45
- data/lib/slate_algolia/parser.rb +73 -52
- data/lib/slate_algolia.rb +4 -2
- data/slate_algolia.gemspec +12 -13
- data/spec/extension_spec.rb +64 -0
- data/spec/fixtures/base/build/index.html +309 -0
- data/spec/fixtures/base/config.rb +2 -0
- data/spec/fixtures/base/source/includes/_errors.md +20 -0
- data/spec/fixtures/base/source/index.html.md +188 -0
- data/spec/fixtures/base/source/layouts/layout.html.erb +81 -0
- data/spec/index_spec.rb +131 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/fixture.rb +17 -0
- data/spec/support/given.rb +26 -0
- metadata +24 -4
- data/features/support/env.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7501fd269fa65aa278b4e78674e7fcb2236ac63
|
4
|
+
data.tar.gz: b181aa3c1968582c0a5b817cd5ee9c99bb042b57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/.rubocop.yml
ADDED
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
data/README.md
CHANGED
@@ -4,7 +4,26 @@
|
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
|
-
|
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 '
|
2
|
-
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'rubocop/rake_task'
|
3
3
|
|
4
|
-
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
RuboCop::RakeTask.new(:style)
|
5
6
|
|
6
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
data/lib/slate_algolia/index.rb
CHANGED
@@ -1,51 +1,68 @@
|
|
1
1
|
require 'algoliasearch'
|
2
2
|
|
3
3
|
module Middleman
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
data/lib/slate_algolia/parser.rb
CHANGED
@@ -1,58 +1,79 @@
|
|
1
1
|
require 'oga'
|
2
2
|
|
3
3
|
module Middleman
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
5
|
-
::Middleman::SlateAlgolia::Extension
|
7
|
+
::Middleman::SlateAlgolia::Extension
|
6
8
|
end
|
data/slate_algolia.gemspec
CHANGED
@@ -1,21 +1,20 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
2
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.name =
|
6
|
-
s.version =
|
5
|
+
s.name = 'slate_algolia'
|
6
|
+
s.version = '1.0.0'
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
|
-
s.authors = [
|
9
|
-
s.email = [
|
10
|
-
s.summary =
|
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 --
|
14
|
-
s.
|
15
|
-
|
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(
|
19
|
-
s.add_runtime_dependency(
|
20
|
-
s.add_runtime_dependency(
|
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
|