action_policy 0.3.1 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e593d4567bab1e7fec960db2d67d7d659d4b4df92159741b7db715b35b75c790
4
- data.tar.gz: efdc9d9815936a6dcbb0e94b0098050bc30fddae20a3d06f35bf03b91787d500
3
+ metadata.gz: 998666ad64277112c6ddc44e6db9e0aa81b6bbf32994fb675797bcd8e1f9ca70
4
+ data.tar.gz: 5a35c722589841c6b41d6efd1735abe63833db03b9a7aec23e798648a97c5470
5
5
  SHA512:
6
- metadata.gz: c58c58e14c25f4daadc890ab67de43781f33f28485811b05458568b9003b9037911fbaade9332dca0b458dec811138df87fa094564574cd55fae948a09379be8
7
- data.tar.gz: 12db66ef181fa94ec6ac2c48feeb7fe49d12a3b66a9a554c925594b9b622ec3cec94412e48db13406d71733d0ae001f7dece5fedfe56ca11216f6e027754765f
6
+ metadata.gz: 03a5594069a5947566708f3b3ad419d79be8931d38c25195f754d1474fa9b2f3bcf5e0f9e520a828d56c1fcfac762929ae4fdcf852c657f4b6bec973c0559e0b
7
+ data.tar.gz: e41e88d916fdf0832e7b03cdadcf7f6c1a4814c45e6ca76e12094bbcb03a6ea54146a0e676d079a0813caff31f54c3dfe90d0e917ade8c4261e17ec4d55a4221
@@ -1,5 +1,11 @@
1
1
  ## master
2
2
 
3
+ ## 0.3.2 (2019-05-26) 👶
4
+
5
+ - Fixed thread-safety issues with scoping configs. ([@palkan][])
6
+
7
+ Fixes [#75](https://github.com/palkan/action_policy/issues/75).
8
+
3
9
  ## 0.3.1 (2019-05-30)
4
10
 
5
11
  - Fixed bug with missing implicit target and hash like scoping data. ([@palkan][])
data/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  # ActionPolicy
6
6
 
7
- Action Policy is an authorization framework for Ruby and Rails applications.
7
+ Authorization framework for Ruby and Rails applications.
8
+
9
+ Composable. Extensible. Performant.
8
10
 
9
11
  📑 [Documentation](https://actionpolicy.evilmartians.io)
10
12
 
@@ -3,7 +3,8 @@
3
3
 
4
4
  ## Action Policy
5
5
 
6
- > Action Policy is an authorization framework for Ruby and Rails applications.
6
+ > Authorization framework for Ruby and Rails.
7
+ <br>Composable. Extensible. Performant.
7
8
 
8
9
  **NOTE:** this documentation is for the version "0.3.0+".
9
10
 
@@ -20,6 +21,12 @@ Action Policy provides flexible tools to build an _authorization layer_ for your
20
21
 
21
22
  **NOTE:** Action Policy does not force you to use a specific authorization model (i.e., roles, permissions, etc.) and does not provide one. It only answers a single question: **How to verify access?**
22
23
 
24
+ ## Where to go from here?
25
+ - [Quick start](./quick_start.md)
26
+ - [Using with Rails](./rails.md)
27
+ - [Using with other Ruby frameworks](./non_rails.md)
28
+ - [Using with GraphQL Ruby](./graphql.md)
29
+
23
30
  ## Project State
24
31
 
25
32
  The project is being used in production since mid 2018. Major features have been implemented, API has been stabilized. Check out our [development board](https://github.com/palkan/action_policy/projects/1) to see what's coming next.
@@ -0,0 +1,364 @@
1
+ (function () {
2
+ var INDEXS = {};
3
+
4
+ var LOCAL_STORAGE = {
5
+ EXPIRE_KEY: 'docsify.search.expires',
6
+ INDEX_KEY: 'docsify.search.index'
7
+ };
8
+
9
+ function resolveExpireKey(namespace) {
10
+ return namespace ? ((LOCAL_STORAGE.EXPIRE_KEY) + "/" + namespace) : LOCAL_STORAGE.EXPIRE_KEY
11
+ }
12
+ function resolveIndexKey(namespace) {
13
+ return namespace ? ((LOCAL_STORAGE.INDEX_KEY) + "/" + namespace) : LOCAL_STORAGE.INDEX_KEY
14
+ }
15
+
16
+ function escapeHtml(string) {
17
+ var entityMap = {
18
+ '&': '&amp;',
19
+ '<': '&lt;',
20
+ '>': '&gt;',
21
+ '"': '&quot;',
22
+ '\'': '&#39;',
23
+ '/': '&#x2F;'
24
+ };
25
+
26
+ return String(string).replace(/[&<>"'/]/g, function (s) { return entityMap[s]; })
27
+ }
28
+
29
+ function getAllPaths(router) {
30
+ var paths = [];
31
+
32
+ Docsify.dom.findAll('.sidebar-nav a:not(.section-link):not([data-nosearch])').forEach(function (node) {
33
+ var href = node.href;
34
+ var originHref = node.getAttribute('href');
35
+ var path = router.parse(href).path;
36
+
37
+ if (
38
+ path &&
39
+ paths.indexOf(path) === -1 &&
40
+ !Docsify.util.isAbsolutePath(originHref)
41
+ ) {
42
+ paths.push(path);
43
+ }
44
+ });
45
+
46
+ return paths
47
+ }
48
+
49
+ function saveData(maxAge, expireKey, indexKey) {
50
+ localStorage.setItem(expireKey, Date.now() + maxAge);
51
+ localStorage.setItem(indexKey, JSON.stringify(INDEXS));
52
+ }
53
+
54
+ function genIndex(path, content, router, depth) {
55
+ if ( content === void 0 ) content = '';
56
+
57
+ var tokens = window.marked.lexer(content);
58
+ var slugify = window.Docsify.slugify;
59
+ var index = {};
60
+ var slug;
61
+
62
+ tokens.forEach(function (token) {
63
+ if (token.type === 'heading' && token.depth <= depth) {
64
+ slug = router.toURL(path, {id: slugify(token.text)});
65
+ index[slug] = {slug: slug, title: token.text, body: ''};
66
+ } else {
67
+ if (!slug) {
68
+ return
69
+ }
70
+ if (!index[slug]) {
71
+ index[slug] = {slug: slug, title: '', body: ''};
72
+ } else if (index[slug].body) {
73
+ index[slug].body += '\n' + (token.text || '');
74
+ } else {
75
+ index[slug].body = token.text;
76
+ }
77
+ }
78
+ });
79
+ slugify.clear();
80
+ return index
81
+ }
82
+
83
+ /**
84
+ * @param {String} query
85
+ * @returns {Array}
86
+ */
87
+ function search(query) {
88
+ var matchingResults = [];
89
+ var data = [];
90
+ Object.keys(INDEXS).forEach(function (key) {
91
+ data = data.concat(Object.keys(INDEXS[key]).map(function (page) { return INDEXS[key][page]; }));
92
+ });
93
+
94
+ query = query.trim();
95
+ var keywords = query.split(/[\s\-,\\/]+/);
96
+ if (keywords.length !== 1) {
97
+ keywords = [].concat(query, keywords);
98
+ }
99
+
100
+ var loop = function ( i ) {
101
+ var post = data[i];
102
+ var matchesScore = 0;
103
+ var resultStr = '';
104
+ var postTitle = post.title && post.title.trim();
105
+ var postContent = post.body && post.body.trim();
106
+ var postUrl = post.slug || '';
107
+
108
+ if (postTitle) {
109
+ keywords.forEach( function (keyword) {
110
+ // From https://github.com/sindresorhus/escape-string-regexp
111
+ var regEx = new RegExp(
112
+ keyword.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'),
113
+ 'gi'
114
+ );
115
+ var indexTitle = -1;
116
+ var indexContent = -1;
117
+
118
+ indexTitle = postTitle ? postTitle.search(regEx) : -1;
119
+ indexContent = postContent ? postContent.search(regEx) : -1;
120
+
121
+ if (indexTitle >= 0 || indexContent >= 0) {
122
+ matchesScore += indexTitle >= 0 ? 3 : indexContent >= 0 ? 2 : 0;
123
+ if (indexContent < 0) {
124
+ indexContent = 0;
125
+ }
126
+
127
+ var start = 0;
128
+ var end = 0;
129
+
130
+ start = indexContent < 11 ? 0 : indexContent - 10;
131
+ end = start === 0 ? 70 : indexContent + keyword.length + 60;
132
+
133
+ if (end > postContent.length) {
134
+ end = postContent.length;
135
+ }
136
+
137
+ var matchContent =
138
+ '...' +
139
+ escapeHtml(postContent)
140
+ .substring(start, end)
141
+ .replace(regEx, ("<em class=\"search-keyword\">" + keyword + "</em>")) +
142
+ '...';
143
+
144
+ resultStr += matchContent;
145
+ }
146
+ });
147
+
148
+ if (matchesScore > 0) {
149
+ var matchingPost = {
150
+ title: escapeHtml(postTitle),
151
+ content: postContent ? resultStr : '',
152
+ url: postUrl,
153
+ score: matchesScore
154
+ };
155
+
156
+ matchingResults.push(matchingPost);
157
+ }
158
+ }
159
+ };
160
+
161
+ for (var i = 0; i < data.length; i++) loop( i );
162
+
163
+ return matchingResults.sort(function (r1, r2) { return r2.score - r1.score; });
164
+ }
165
+
166
+ function init$1(config, vm) {
167
+ var isAuto = config.paths === 'auto';
168
+
169
+ var expireKey = resolveExpireKey(config.namespace);
170
+ var indexKey = resolveIndexKey(config.namespace);
171
+
172
+ var isExpired = localStorage.getItem(expireKey) < Date.now();
173
+
174
+ INDEXS = JSON.parse(localStorage.getItem(indexKey));
175
+
176
+ if (isExpired) {
177
+ INDEXS = {};
178
+ } else if (!isAuto) {
179
+ return
180
+ }
181
+
182
+ var paths = isAuto ? getAllPaths(vm.router) : config.paths;
183
+ var len = paths.length;
184
+ var count = 0;
185
+
186
+ paths.forEach(function (path) {
187
+ if (INDEXS[path]) {
188
+ return count++
189
+ }
190
+
191
+ Docsify
192
+ .get(vm.router.getFile(path), false, vm.config.requestHeaders)
193
+ .then(function (result) {
194
+ INDEXS[path] = genIndex(path, result, vm.router, config.depth);
195
+ len === ++count && saveData(config.maxAge, expireKey, indexKey);
196
+ });
197
+ });
198
+ }
199
+
200
+ var NO_DATA_TEXT = '';
201
+ var options;
202
+
203
+ function style() {
204
+ var code = "\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .input-wrap {\n display: flex;\n align-items: center;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 0 7px;\n line-height: 36px;\n font-size: 14px;\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button,\n.search input {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n.search .clear-button {\n width: 36px;\n text-align: right;\n display: none;\n}\n\n.search .clear-button.show {\n display: block;\n}\n\n.search .clear-button svg {\n transform: scale(.5);\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}\n\n.app-name.hide, .sidebar-nav.hide {\n display: none;\n}";
205
+
206
+ Docsify.dom.style(code);
207
+ }
208
+
209
+ function tpl(defaultValue) {
210
+ if ( defaultValue === void 0 ) defaultValue = '';
211
+
212
+ var html =
213
+ "<div class=\"input-wrap\">\n <input type=\"search\" value=\"" + defaultValue + "\" />\n <div class=\"clear-button\">\n <svg width=\"26\" height=\"24\">\n <circle cx=\"12\" cy=\"12\" r=\"11\" fill=\"#ccc\" />\n <path stroke=\"white\" stroke-width=\"2\" d=\"M8.25,8.25,15.75,15.75\" />\n <path stroke=\"white\" stroke-width=\"2\"d=\"M8.25,15.75,15.75,8.25\" />\n </svg>\n </div>\n </div>\n <div class=\"results-panel\"></div>\n </div>";
214
+ var el = Docsify.dom.create('div', html);
215
+ var aside = Docsify.dom.find('aside');
216
+
217
+ Docsify.dom.toggleClass(el, 'search');
218
+ Docsify.dom.before(aside, el);
219
+ }
220
+
221
+ function doSearch(value) {
222
+ var $search = Docsify.dom.find('div.search');
223
+ var $panel = Docsify.dom.find($search, '.results-panel');
224
+ var $clearBtn = Docsify.dom.find($search, '.clear-button');
225
+ var $sidebarNav = Docsify.dom.find('.sidebar-nav');
226
+ var $appName = Docsify.dom.find('.app-name');
227
+
228
+ if (!value) {
229
+ $panel.classList.remove('show');
230
+ $clearBtn.classList.remove('show');
231
+ $panel.innerHTML = '';
232
+
233
+ if (options.hideOtherSidebarContent) {
234
+ $sidebarNav.classList.remove('hide');
235
+ $appName.classList.remove('hide');
236
+ }
237
+ return
238
+ }
239
+ var matchs = search(value);
240
+
241
+ var html = '';
242
+ matchs.forEach(function (post) {
243
+ html += "<div class=\"matching-post\">\n<a href=\"" + (post.url) + "\">\n<h2>" + (post.title) + "</h2>\n<p>" + (post.content) + "</p>\n</a>\n</div>";
244
+ });
245
+
246
+ $panel.classList.add('show');
247
+ $clearBtn.classList.add('show');
248
+ $panel.innerHTML = html || ("<p class=\"empty\">" + NO_DATA_TEXT + "</p>");
249
+ if (options.hideOtherSidebarContent) {
250
+ $sidebarNav.classList.add('hide');
251
+ $appName.classList.add('hide');
252
+ }
253
+ }
254
+
255
+ function bindEvents() {
256
+ var $search = Docsify.dom.find('div.search');
257
+ var $input = Docsify.dom.find($search, 'input');
258
+ var $inputWrap = Docsify.dom.find($search, '.input-wrap');
259
+
260
+ var timeId;
261
+ // Prevent to Fold sidebar
262
+ Docsify.dom.on(
263
+ $search,
264
+ 'click',
265
+ function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); }
266
+ );
267
+ Docsify.dom.on($input, 'input', function (e) {
268
+ clearTimeout(timeId);
269
+ timeId = setTimeout(function (_) { return doSearch(e.target.value.trim()); }, 100);
270
+ });
271
+ Docsify.dom.on($inputWrap, 'click', function (e) {
272
+ // Click input outside
273
+ if (e.target.tagName !== 'INPUT') {
274
+ $input.value = '';
275
+ doSearch();
276
+ }
277
+ });
278
+ }
279
+
280
+ function updatePlaceholder(text, path) {
281
+ var $input = Docsify.dom.getNode('.search input[type="search"]');
282
+
283
+ if (!$input) {
284
+ return
285
+ }
286
+ if (typeof text === 'string') {
287
+ $input.placeholder = text;
288
+ } else {
289
+ var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0];
290
+ $input.placeholder = text[match];
291
+ }
292
+ }
293
+
294
+ function updateNoData(text, path) {
295
+ if (typeof text === 'string') {
296
+ NO_DATA_TEXT = text;
297
+ } else {
298
+ var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0];
299
+ NO_DATA_TEXT = text[match];
300
+ }
301
+ }
302
+
303
+ function updateOptions(opts) {
304
+ options = opts;
305
+ }
306
+
307
+ function init(opts, vm) {
308
+ var keywords = vm.router.parse().query.s;
309
+
310
+ updateOptions(opts);
311
+ style();
312
+ tpl(keywords);
313
+ bindEvents();
314
+ keywords && setTimeout(function (_) { return doSearch(keywords); }, 500);
315
+ }
316
+
317
+ function update(opts, vm) {
318
+ updateOptions(opts);
319
+ updatePlaceholder(opts.placeholder, vm.route.path);
320
+ updateNoData(opts.noData, vm.route.path);
321
+ }
322
+
323
+ var CONFIG = {
324
+ placeholder: 'Type to search',
325
+ noData: 'No Results!',
326
+ paths: 'auto',
327
+ depth: 2,
328
+ maxAge: 86400000, // 1 day
329
+ hideOtherSidebarContent: false,
330
+ namespace: undefined
331
+ };
332
+
333
+ var install = function (hook, vm) {
334
+ var util = Docsify.util;
335
+ var opts = vm.config.search || CONFIG;
336
+
337
+ if (Array.isArray(opts)) {
338
+ CONFIG.paths = opts;
339
+ } else if (typeof opts === 'object') {
340
+ CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto';
341
+ CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge;
342
+ CONFIG.placeholder = opts.placeholder || CONFIG.placeholder;
343
+ CONFIG.noData = opts.noData || CONFIG.noData;
344
+ CONFIG.depth = opts.depth || CONFIG.depth;
345
+ CONFIG.hideOtherSidebarContent = opts.hideOtherSidebarContent || CONFIG.hideOtherSidebarContent;
346
+ CONFIG.namespace = opts.namespace || CONFIG.namespace;
347
+ }
348
+
349
+ var isAuto = CONFIG.paths === 'auto';
350
+
351
+ hook.mounted(function (_) {
352
+ init(CONFIG, vm);
353
+ !isAuto && init$1(CONFIG, vm);
354
+ });
355
+ hook.doneEach(function (_) {
356
+ update(CONFIG, vm);
357
+ isAuto && init$1(CONFIG, vm);
358
+ });
359
+ };
360
+
361
+ $docsify.plugins = [].concat(install, $docsify.plugins);
362
+
363
+ }());
364
+
@@ -56,18 +56,23 @@ a {
56
56
  text-align: center;
57
57
  }
58
58
 
59
-
60
- aside.sidebar {
59
+ .sidebar {
61
60
  border: none;
62
61
  background-color: var(--theme-color-secondary);
63
62
  color: #fff;
64
63
  width: 20%;
64
+ display: flex;
65
+ flex-direction: column;
65
66
  }
66
67
 
67
68
  body.close .sidebar {
68
69
  transform: translateX(-100%);
69
70
  }
70
71
 
72
+ .sidebar .sidebar-nav {
73
+ order: 3;
74
+ }
75
+
71
76
  .sidebar ul li a {
72
77
  color: rgba(255,255,255,0.8);
73
78
  color: var(--theme-color-light);
@@ -89,7 +94,32 @@ body.close .sidebar {
89
94
  }
90
95
 
91
96
  .sidebar > h1 {
97
+ order: 1;
92
98
  font-size: 2rem;
99
+ margin-top: 1.5rem;
100
+ }
101
+
102
+ /* search plugin related */
103
+
104
+ .sidebar .search {
105
+ order: 2;
106
+ margin: 1.5rem 0 0 0;
107
+ border: none;
108
+ margin-top: 20px;
109
+ margin-bottom: 20px;
110
+ padding: 6px;
111
+ }
112
+
113
+ .sidebar .search .input-wrap {
114
+ border: none;
115
+ position: relative;
116
+ }
117
+
118
+ .sidebar .search .clear-button.show {
119
+ display: block;
120
+ position: absolute;
121
+ right: 4px;
122
+ top: 8px;
93
123
  }
94
124
 
95
125
  body .sidebar-toggle {
@@ -12,6 +12,7 @@ class ActionPolicy::Base
12
12
  include ActionPolicy::Policy::PreCheck
13
13
  include ActionPolicy::Policy::Reasons
14
14
  include ActionPolicy::Policy::Aliases
15
+ include ActionPolicy::Policy::Scoping
15
16
  include ActionPolicy::Policy::Cache
16
17
  include ActionPolicy::Policy::CachedApply
17
18
  include ActionPolicy::Policy::Defaults
@@ -15,7 +15,7 @@ module ActionPolicy
15
15
  module Draper
16
16
  def policy_for(record:, **opts)
17
17
  # From https://github.com/GoodMeasuresLLC/draper-cancancan/blob/master/lib/draper/cancancan.rb
18
- record = record.model while record.is_a?(Draper::Decorator)
18
+ record = record.model while record.is_a?(::Draper::Decorator)
19
19
  super(record: record, **opts)
20
20
  end
21
21
  end
@@ -30,10 +30,14 @@
30
30
  loadSidebar: true,
31
31
  subMaxLevel: 3,
32
32
  ga: 'UA-104346673-3',
33
- auto2top:true
33
+ auto2top: true,
34
+ search: {
35
+ namespace: 'action-policy'
36
+ }
34
37
  }
35
38
  </script>
36
39
  <script src="assets/docsify.min.js"></script>
40
+ <script src="assets/docsify-search.js"></script>
37
41
  <script src="assets/prism-ruby.min.js"></script>
38
42
  </body>
39
43
  </html>
@@ -13,7 +13,10 @@ module ActionPolicy
13
13
 
14
14
  def initialize(target, message = nil)
15
15
  @target = target
16
- @message = message || "Couldn't find policy class for #{target.inspect}"
16
+ @message =
17
+ message ||
18
+ "Couldn't find policy class for #{target.inspect}" \
19
+ "#{target.is_a?(Module) ? "" : " (#{target.class})"}"
17
20
  end
18
21
  end
19
22
 
@@ -124,15 +124,14 @@ module ActionPolicy
124
124
  def scoping_handlers
125
125
  return @scoping_handlers if instance_variable_defined?(:@scoping_handlers)
126
126
 
127
- @scoping_handlers = Hash.new { |h, k| h[k] = {} }
128
-
129
- if superclass.respond_to?(:scoping_handlers)
130
- superclass.scoping_handlers.each do |k, v|
131
- @scoping_handlers[k] = v.dup
127
+ @scoping_handlers =
128
+ Hash.new { |h, k| h[k] = {} }.tap do |handlers|
129
+ if superclass.respond_to?(:scoping_handlers)
130
+ superclass.scoping_handlers.each do |k, v|
131
+ handlers[k] = v.dup
132
+ end
133
+ end
132
134
  end
133
- end
134
-
135
- @scoping_handlers
136
135
  end
137
136
 
138
137
  # Define scope type matcher.
@@ -149,11 +148,12 @@ module ActionPolicy
149
148
  def scope_matchers
150
149
  return @scope_matchers if instance_variable_defined?(:@scope_matchers)
151
150
 
152
- @scope_matchers = []
153
-
154
- @scope_matchers = superclass.scope_matchers.dup if superclass.respond_to?(:scope_matchers)
155
-
156
- @scope_matchers
151
+ @scope_matchers =
152
+ if superclass.respond_to?(:scope_matchers)
153
+ superclass.scope_matchers.dup
154
+ else
155
+ []
156
+ end
157
157
  end
158
158
  end
159
159
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionPolicy
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-30 00:00:00.000000000 Z
11
+ date: 2019-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -165,6 +165,7 @@ files:
165
165
  - docs/README.md
166
166
  - docs/_sidebar.md
167
167
  - docs/aliases.md
168
+ - docs/assets/docsify-search.js
168
169
  - docs/assets/docsify.min.js
169
170
  - docs/assets/fonts/FiraCode-Medium.woff
170
171
  - docs/assets/fonts/FiraCode-Regular.woff