jekyll-notion 2.4.3 → 3.0.0.beta1

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: a2e9b1115040d4ee01312d20ad5f4941ecc9d3d06f5030e81d923c6c1ebbc42f
4
- data.tar.gz: 404ad2116038bdb51c961123954076e5fd141977433c90ec451259e23ab1dadd
3
+ metadata.gz: ed820907d9684cde0edb6ec147eec63f01c5d0c84ece59e2eae336ffeae89576
4
+ data.tar.gz: d1007a311273a529246618a538a658ac88e0ac688160294cd730053f721ddb46
5
5
  SHA512:
6
- metadata.gz: 9541cf17c24c1abda04e6fcead84d6b40e561e41307b8200e53b97d07f86691c5f50fd12616c33b4b1a8d4bd52197ae4e0e94621b409426e370a1837fecef163
7
- data.tar.gz: 8ce7987c435b601e21b390424791099f0b63e0c6c3261ba80415d28e4a89965eca0ed6a923a41f0b45d292c9f0a4ba7e924e610fc3376b176a4dd258c9614465
6
+ metadata.gz: 7492e73cf69027072ce70e458446c27834344e33bccaab9d4e77b5f8f4d6703993b39bde77c96c29658ddfed93e69f49c23882bb4fe803148faa35ca78bca4a4
7
+ data.tar.gz: 3a0c7bf81c0c2ac03a1b46912dfbb1c499caf5db9621716bfb1a292c697a31dde4b50706b6b6558a9920da69c30ee95a5edf070d7803cd7befac8a20fd376508
data/README.md CHANGED
@@ -1,58 +1,94 @@
1
+ <img src="https://ik.imagekit.io/gxidvqvc9/jekyll_notion_logo_Thmlxy7GZ.png?updatedAt=1756230501479" width="200">
2
+
1
3
  # jekyll-notion
2
4
 
3
- Import notion pages to jekyll.
5
+ > [!WARNING]
6
+ > The **main branch** is under active development for version 3.
7
+ > For the current **stable release**, please check out the [v2.x.x branch](https://github.com/emoriarty/jekyll-notion/tree/v2.x.x).
4
8
 
5
- You can learn more about how to use jekyll-notion with the following links:
9
+ Import [Notion](https://www.notion.so) pages into
10
+ [Jekyll](https://jekyllrb.com/).
6
11
 
7
- * [Load notion pages in jekyll](https://enrq.me/dev/2022/03/20/load-notion-pages-in-jekyll/)
8
- * [Managing Jekyll posts in Notion](https://enrq.me/dev/2022/03/24/managing-jekyll-posts-in-notion/)
9
- * [Embedding videos with jekyll-notion](https://enrq.me/dev/2023/03/31/embedding-videos-with-jekyll-notion/)
12
+ 📚 Learn more with these guides:
13
+ - [Load Notion pages in
14
+ Jekyll](https://enrq.me/dev/2022/03/20/load-notion-pages-in-jekyll/)
15
+ - [Managing Jekyll posts in
16
+ Notion](https://enrq.me/dev/2022/03/24/managing-jekyll-posts-in-notion/)
17
+ - [Embedding videos with
18
+ jekyll-notion](https://enrq.me/dev/2023/03/31/embedding-videos-with-jekyll-notion/)
10
19
 
11
20
  ## Installation
12
21
 
13
- Use gem to install.
14
- ```bash
15
- $ gem install 'jekyll-notion'
22
+ Install via RubyGems:
23
+
24
+ ``` bash
25
+ gem install jekyll-notion
16
26
  ```
17
27
 
18
- Or add it to the `Gemfile`.
19
- ```ruby
28
+ Or add it to your `Gemfile`:
29
+
30
+ ``` ruby
20
31
  # Gemfile
21
32
  gem 'jekyll-notion'
22
33
  ```
23
34
 
24
- And update your jekyll plugins property in `_config.yml`.
35
+ > \[!IMPORTANT\]\
36
+ > If you are using **jekyll-archives**, list `jekyll-notion` *before*
37
+ > `jekyll-archives` in the Gemfile. Otherwise, imported pages will not
38
+ > be picked up.\
39
+ > See the discussion
40
+ > [here](https://github.com/emoriarty/jekyll-notion/issues/95#issuecomment-2732112458).
41
+
42
+ Then enable the plugin in `_config.yml`:
25
43
 
26
- ```yml
44
+ ``` yaml
27
45
  plugins:
28
46
  - jekyll-notion
29
47
  ```
30
-
31
48
  ## Usage
32
49
 
33
- Before using the gem, create an integration and generate a secret token. For more in-depth instructions, refer to the Notion "Getting Started" [guide](https://developers.notion.com/docs/getting-started).
50
+ Before using the gem, [create a Notion
51
+ integration](https://developers.notion.com/docs/getting-started) and
52
+ generate a secret token.
34
53
 
35
- Once you have your secret token, make sure to export it into an environment variable named `NOTION_TOKEN`.
54
+ Export the token as an environment variable:
36
55
 
37
- ```bash
38
- $ export NOTION_TOKEN=<secret_...>
56
+ ``` bash
57
+ export NOTION_TOKEN=<secret_...>
58
+ ```
59
+
60
+ ### Environment Variables
61
+
62
+ The plugin supports the following environment variables for configuration:
63
+
64
+ - **`NOTION_TOKEN`** (required): Your Notion integration secret token
65
+ - **`JEKYLL_NOTION_CACHE`**: Fallback cache setting when not specified in `_config.yml` (`1`, `true`, `yes` to enable; `0`, `false`, `no` to disable)
66
+ - **`JEKYLL_NOTION_CACHE_DIR`**: Fallback cache directory when not specified in `_config.yml` (defaults to `.cache/jekyll-notion/vcr_cassettes`)
67
+
68
+ Example usage:
69
+ ``` bash
70
+ export NOTION_TOKEN=secret_abc123...
71
+ export JEKYLL_NOTION_CACHE=false
72
+ export JEKYLL_NOTION_CACHE_DIR=/tmp/my-custom-cache
39
73
  ```
40
74
 
41
75
  ### Databases
42
76
 
43
- Once the [notion database](https://developers.notion.com/docs/working-with-databases) has been shared, specify the database `id` in the `_config.yml` file as follows.
77
+ Share a [Notion
78
+ database](https://developers.notion.com/docs/working-with-databases),
79
+ then specify its `id` in `_config.yml`:
44
80
 
45
- ```yml
81
+ ``` yaml
46
82
  notion:
47
83
  databases:
48
84
  - id: 5cfed4de3bdc4f43ae8ba653a7a2219b
49
85
  ```
50
86
 
51
- By default, the notion pages in the database will be loaded into the `posts` collection.
87
+ By default, entries will be added to the `posts` collection.
52
88
 
53
- We can also define __multiple databases__ as follows.
89
+ You can also define **multiple databases**:
54
90
 
55
- ```yml
91
+ ``` yaml
56
92
  collections:
57
93
  - recipes
58
94
  - films
@@ -62,22 +98,26 @@ notion:
62
98
  - id: b0e688e199af4295ae80b67eb52f2e2f
63
99
  - id: 2190450d4cb34739a5c8340c4110fe21
64
100
  collection: recipes
65
- - id: e42383cd49754897b967ce453760499f
101
+ - id: e42383cd49754897b967ce453760499f
66
102
  collection: films
67
103
  ```
68
104
 
69
- After running `jekyll build` (or `serve`) command, the `posts`, `recipes` and `films` collections will be loaded with pages from the notion databases.
105
+ After running `jekyll build` or `jekyll serve`, the `posts`, `recipes`,
106
+ and `films` collections will contain pages from the specified databases.
70
107
 
71
108
  #### Database options
72
109
 
73
- Each dabatase support the following options.
110
+ Each database supports the following options:
74
111
 
75
- * `id`: the notion database unique identifier,
76
- * `collection`: the collection each page belongs to (posts by default),
77
- * `filter`: the database [filter property](https://developers.notion.com/reference/post-database-query-filter),
78
- * `sorts`: the database [sorts criteria](https://developers.notion.com/reference/post-database-query-sort),
112
+ - `id`: the unique Notion database ID
113
+ - `collection`: which collection to assign pages to (`posts` by
114
+ default)
115
+ - `filter`: a database
116
+ [filter](https://developers.notion.com/reference/post-database-query-filter)
117
+ - `sorts`: database [sorting
118
+ criteria](https://developers.notion.com/reference/post-database-query-sort)
79
119
 
80
- ```yml
120
+ ``` yaml
81
121
  notion:
82
122
  databases:
83
123
  - id: e42383cd49754897b967ce453760499f
@@ -86,25 +126,29 @@ notion:
86
126
  sorts: [{ "timestamp": "created_time", "direction": "ascending" }]
87
127
  ```
88
128
 
89
- #### Posts date
129
+ #### Post dates
90
130
 
91
- The `created_time` property of a notion page is used to set the date in the post filename. This is the date used for the `date` variable of the [predefined variables for posts](https://jekyllrb.com/docs/front-matter/#predefined-variables-for-posts).
131
+ By default, the Notion page `created_time` property sets the post
132
+ filename date. This value is used for Jekyll's [`date`
133
+ variable\`](https://jekyllrb.com/docs/front-matter/#predefined-variables-for-posts).
92
134
 
93
- It's important to note that the `created_time` cannot be modifed. However, if you wish to change the date of a post, you can create a new page property named "date" (or "Date"). This way, the posts collection will use the `date` property for the post date variable instead of the `created_time`.
135
+ Since `created_time` cannot be modified, you can override it by adding a
136
+ custom Notion property named `date` (or `Date`). That property will be
137
+ used instead.
94
138
 
95
139
  ### Pages
96
140
 
97
- Individual Notion pages can also be loaded into Jekyll. Just define the `page` property as follows.
141
+ You can also load individual Notion pages:
98
142
 
99
- ```yml
143
+ ``` yaml
100
144
  notion:
101
145
  pages:
102
146
  - id: 5cfed4de3bdc4f43ae8ba653a7a2219b
103
147
  ```
104
148
 
105
- As databases, we can set up multiple pages.
149
+ Multiple pages are supported:
106
150
 
107
- ```yaml
151
+ ``` yaml
108
152
  notion:
109
153
  pages:
110
154
  - id: e42383cd49754897b967ce453760499f
@@ -112,15 +156,19 @@ notion:
112
156
  - id: 2190450d4cb34739a5c8340c4110fe21
113
157
  ```
114
158
 
115
- The filename of the generated page is the notion page title. Check [below](#page-filename) for more info.
159
+ The generated filename is based on the Notion page title (see [Page
160
+ filename](#page-filename)).
116
161
 
117
- All properties assigned to a notion page will be interpreted by jekyll as front matter. For example, if the [permalink](https://jekyllrb.com/docs/permalinks/#front-matter) property is set to `/about/` in the notion page, jekyll will use it to create the corresponding path at the output directory at `/about/index.html`.
162
+ All page properties are exposed as Jekyll front matter. For example, if
163
+ a page has a `permalink` property set to `/about/`, Jekyll will generate
164
+ `/about/index.html`.
118
165
 
119
166
  ### Data
120
167
 
121
- Instead of storing the notion pages in a collection or in the pages list, you can assign them to the data object.Just declare the `data` property next to the page or database id.
168
+ Instead of adding Notion pages to collections or `pages`, you can store
169
+ them under the Jekyll **data object** using the `data` option:
122
170
 
123
- ```yml
171
+ ``` yaml
124
172
  notion:
125
173
  databases:
126
174
  - id: b0e688e199af4295ae80b67eb52f2e2f
@@ -132,91 +180,138 @@ notion:
132
180
  data: about
133
181
  ```
134
182
 
135
- Page properties and body of the notion page are stored as a hash object.
183
+ Each page is stored as a hash. The page body is available under the
184
+ `content` key.
136
185
 
137
- Data objects can be accessed as follows.
186
+ Example:
138
187
 
139
- ```html
188
+ ``` html
140
189
  <ul>
141
- {% for film in site.data.films %}
142
- <li>{{ film.title }}</li>
143
- {% endfor %}
190
+ {% for film in site.data.films %}
191
+ <li>{{ film.title }}</li>
192
+ {% endfor %}
144
193
  </ul>
145
- ```
146
194
 
147
- Notice, the page body is stored in the key `content`.
148
-
149
- ```html
150
195
  {{ site.data.about.content }}
151
196
  ```
152
197
 
153
- The rest of properties are mapped as expected. For more info go to [notion properties](#notion-properties).
198
+ Other properties are mapped normally (see [Notion
199
+ properties](#notion-properties)).
154
200
 
155
- ### Watch
201
+ ### Cache
156
202
 
157
- By default, databases are only requested during the first build. Subsequent builds use the results from the cache.
203
+ All Notion requests are cached locally with the [VCR](https://github.com/vcr/vcr) gem to speed up rebuilds.
204
+ The first build fetches from the Notion API; subsequent builds reuse the cache.
158
205
 
159
- Set `fetch_on_watch` to true to allow request on each rebuild.
206
+ The cache mechanism provides:
160
207
 
161
- ```yml
162
- notion:
163
- fetch_on_watch: true
164
- databases:
165
- - id: e42383cd49754897b967ce453760499f
208
+ - Per-page cache files that include the Notion page title + ID, making them easy to identify.
209
+ - Page-level deletion: remove a single cached page without affecting others.
210
+ - Databases fetched on every rebuild: new content in Notion is always discovered, while cached pages prevent unnecessary re-fetches.
211
+
212
+ **Example cached file (title + ID):**
213
+ ```bash
214
+ .cache/jekyll-notion/vcr_cassettes/my-page-title-e42383cd49754897b967ce453760499f.yml
166
215
  ```
167
216
 
168
- And that's all. Each page in the notion database will be included in the selected collection.
217
+ #### Cache folder
169
218
 
170
- ### Cache
219
+ Default: `.cache/jekyll-notion/vcr_cassettes`
171
220
 
172
- Starting from version 2.4.0, every request to Notion is cached locally. The cache enables the retrieval of Notion resources only during the first request. Subsequent requests are fetched from the cache, which can significantly reduce build times.
221
+ You can override the cache directory in two ways:
173
222
 
174
- The cache mechanism is based on the [vcr](https://github.com/vcr/vcr) gem, which records HTTP requests. Every Notion resource, whether it is a database or page, is stored in an independent file using the document ID as the filename. For example, a database ID e42383cd49754897b967ce453760499f will be stored in the following path:
223
+ **Option 1: Configuration file** (in `_config.yml`):
224
+ ``` yaml
225
+ notion:
226
+ cache_dir: another/folder
227
+ ```
175
228
 
176
- ```bash
177
- .cache/jekyll-notion/vcr_cassetes/e42383cd49754897b967ce453760499f.yml
229
+ **Option 2: Environment variable**:
230
+ ``` bash
231
+ export JEKYLL_NOTION_CACHE_DIR=/path/to/custom/cache
178
232
  ```
179
233
 
180
- **Note: The `cache` option invalidates the fetch_on_watch feature.**
234
+ The `_config.yml` setting takes precedence over the environment variable.
235
+ Both relative and absolute paths are supported - relative paths are resolved
236
+ from the project root.
181
237
 
182
- #### Cache folder
238
+ #### Cleaning the cache
239
+
240
+ - Delete the entire cache folder to reset everything.
241
+ - Or delete a single cached page file to refresh only that page.
183
242
 
184
- By default, the cache folder is `.cache/jekyll-notion/vcr_cassetes`, but you can change this folder by setting the `cache_dir` property in the `_config.yml` file as follows.
243
+ #### Disabling the cache
185
244
 
186
- ```yaml
245
+ To disable caching entirely:
246
+
247
+ ``` yaml
187
248
  notion:
188
- cache_dir: another/folder
249
+ cache: false
189
250
  ```
190
251
 
191
- The path must be relative to the working folder.
252
+ Or use the `JEKYLL_NOTION_CACHE` environment variable:
192
253
 
193
- #### Cleaning cache
254
+ ```bash
255
+ export JEKYLL_NOTION_CACHE=false # or 0, no
256
+ ```
194
257
 
195
- To clear the cache, delete the cache folder. If you want to remove a specific cache file, locate the file that matches the Notion resource ID and delete it.
258
+ ## Sensitive data
196
259
 
197
- #### Disabling cache
260
+ The cache stores full request and response payloads from the Notion API.
261
+ This may include sensitive information such as authentication tokens, URLs, or private content.
198
262
 
199
- If you're not interested in the cache or you just want to disable it, set the ˋcache` option to false.
263
+ If you intend to store cached files in version control or share them with others, be mindful of what they contain.
264
+ By default, jekyll-notion automatically redacts the `NOTION_TOKEN` from all cache files.
265
+ If you need to mask additional values, you can configure [VCR filters](https://benoittgt.github.io/vcr/#/configuration/filter_sensitive_data?id=filter-sensitive-data).
200
266
 
201
- ```yaml
202
- notion:
203
- cache: false
267
+ For example, add a file `_plugins/vcr_config.rb`:
268
+
269
+ ```ruby
270
+ VCR.configure do |config|
271
+ # Already handled by jekyll-notion: NOTION_TOKEN
272
+ # Example of masking a custom header or property:
273
+ config.filter_sensitive_data("[MASKED]") do |interaction|
274
+ interaction.request.headers["User-Agent"]&.first
275
+ end
276
+ end
204
277
  ```
205
278
 
279
+ This file will be automatically picked up by Jekyll and merged into the VCR configuration provided by jekyll-notion.
280
+
281
+ You can add filters for headers, query parameters, or any other values you don’t want exposed in the cache.
282
+
206
283
  ## Notion properties
207
284
 
208
- Notion page properties are set for each document in the front matter.
285
+ Notion page properties are mapped into each Jekyll document's front
286
+ matter.
209
287
 
210
- Please, refer to the [notion_to_md](https://github.com/emoriarty/notion_to_md/) gem to learn more.
288
+ See the companion gem
289
+ [notion_to_md](https://github.com/emoriarty/notion_to_md/) for details.
211
290
 
212
291
  ## Page filename
213
292
 
214
- There are two kinds of documents in Jekyll: posts and others.
293
+ Jekyll distinguishes between **posts** and **other documents**:
294
+
295
+ - **Posts**: filenames follow the format
296
+ `YEAR-MONTH-DAY-title.MARKUP`, where the date comes from the Notion
297
+ `created_time` (or the `date` property if present).\
298
+ - **Other documents**: filenames are derived from the Notion page
299
+ title.
215
300
 
216
- When the document is a post, the filename format contains the `created_time` property plus the page title as specified in [jekyll docs](https://jekyllrb.com/docs/posts/#creating-posts).
301
+ ## Testing
217
302
 
303
+ Run the test suite:
304
+
305
+ ```bash
306
+ bundle exec rspec # Run all tests
307
+ bundle exec rspec spec/path/to/test # Run specific test file
218
308
  ```
219
- YEAR-MONTH-DAY-title.MARKUP
309
+
310
+ ### Golden Files
311
+
312
+ Tests use golden files to validate generated output against known-good snapshots. Update snapshots when expected output changes:
313
+
314
+ ```bash
315
+ UPDATE_GOLDEN=1 bundle exec rspec
220
316
  ```
221
317
 
222
- The filename for any other document is the page title.
@@ -1,33 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
3
+ require_relative "cassette_manager"
4
4
 
5
5
  module JekyllNotion
6
6
  module Cacheable
7
- def self.setup(cache_dir)
8
- # Using VCR to record and playback Notion API responses for caching
9
- VCR.configure do |config|
10
- config.cassette_library_dir = cache_path(cache_dir)
11
- config.hook_into :faraday # Faraday is used by notion-ruby-client gem
12
- config.filter_sensitive_data("<NOTION_TOKEN>") { ENV["NOTION_TOKEN"] }
13
- config.allow_http_connections_when_no_cassette = true
14
- config.default_cassette_options = {
15
- :allow_playback_repeats => true,
16
- :record => :new_episodes,
17
- }
7
+ class << self
8
+ def configure(cache_dir:, cache_enabled:)
9
+ @cache_dir = cache_dir
10
+ @cache_enabled = cache_enabled
11
+
12
+ configure_vcr
13
+ end
14
+
15
+ def cache_dir
16
+ # Always return VCR's configured directory to ensure consistency
17
+ # between CassetteManager operations and VCR cassette storage
18
+ VCR.configuration.cassette_library_dir
19
+ end
20
+
21
+ def enabled?
22
+ @cache_enabled
18
23
  end
19
- end
20
24
 
21
- def self.cache_path(path = nil)
22
- if path.nil?
23
- File.join(Dir.getwd, ".cache", "jekyll-notion", "vcr_cassettes")
24
- else
25
- File.join(Dir.getwd, path)
25
+ private
26
+
27
+ def configure_vcr
28
+ # Determine the directory to use based on configuration and environment
29
+ target_dir = @cache_dir || ENV["JEKYLL_NOTION_CACHE_DIR"] || File.join(Dir.pwd, ".cache",
30
+ "jekyll-notion", "vcr_cassettes")
31
+
32
+ VCR.configure do |config|
33
+ config.cassette_library_dir = target_dir
34
+ config.hook_into :faraday # Faraday is used by notion-ruby-client gem
35
+ config.filter_sensitive_data("<REDACTED>") { ENV.fetch("NOTION_TOKEN", nil) }
36
+ config.allow_http_connections_when_no_cassette = true
37
+ config.default_cassette_options = {
38
+ :allow_playback_repeats => true,
39
+ :record => :new_episodes,
40
+ }
41
+ end
26
42
  end
27
43
  end
28
44
 
29
- def generate(*args)
30
- VCR.use_cassette(resource_id) { super(*args) }
45
+ def call
46
+ return super unless JekyllNotion::Cacheable.enabled?
47
+
48
+ cassette_manager = CassetteManager.new(JekyllNotion::Cacheable.cache_dir)
49
+ cassette_name = cassette_manager.cassette_name_for(id)
50
+ result = nil
51
+
52
+ VCR.use_cassette(
53
+ cassette_name,
54
+ :record => :new_episodes,
55
+ :allow_playback_repeats => true
56
+ ) do
57
+ result = super
58
+ end
59
+
60
+ cassette_manager.update_after_call(id, result)
61
+ result
31
62
  end
32
63
  end
33
64
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module JekyllNotion
7
+ class CassetteManager
8
+ INDEX_BASENAME = ".pages_index.yml"
9
+ PAGES_DIR = "pages"
10
+
11
+ def initialize(cache_dir)
12
+ @cache_dir = cache_dir
13
+ end
14
+
15
+ def cassette_name_for(id)
16
+ sanitized_id = sanitize_id(id)
17
+
18
+ # a) index mapping wins
19
+ if (pretty = load_index_yaml[sanitized_id]) && File.exist?(cassette_path(pretty))
20
+ return pretty
21
+ end
22
+
23
+ # b) any existing "*-id.yml" (handles prior runs / title changes)
24
+ if (found = find_existing_by_id(sanitized_id))
25
+ return found
26
+ end
27
+
28
+ # c) fallback to plain id (first run)
29
+ "#{PAGES_DIR}/#{sanitized_id}"
30
+ end
31
+
32
+ def update_after_call(id, result)
33
+ return unless (title = extract_title(result)).to_s != ""
34
+
35
+ sanitized_id = sanitize_id(id)
36
+ current_cassette = cassette_name_for(sanitized_id)
37
+ pretty_name = "#{PAGES_DIR}/#{sanitize_title(title)}-#{sanitized_id}"
38
+
39
+ rename_cassette_if_needed(:from => current_cassette, :to => pretty_name)
40
+ update_index_yaml(:id => sanitized_id, :pretty => pretty_name)
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :cache_dir
46
+
47
+ def cassette_path(name)
48
+ File.join(cache_dir, "#{name}.yml")
49
+ end
50
+
51
+ def find_existing_by_id(id)
52
+ matches = Dir[File.join(cache_dir, "pages", "*-#{id}.yml")]
53
+ return nil if matches.empty?
54
+
55
+ File.join(PAGES_DIR, File.basename(matches.first, ".yml"))
56
+ end
57
+
58
+ def rename_cassette_if_needed(from:, to:)
59
+ return if from == to
60
+
61
+ src = cassette_path(from)
62
+ dst = cassette_path(to)
63
+ return unless File.exist?(src)
64
+ return if File.exist?(dst)
65
+
66
+ FileUtils.mkdir_p(File.dirname(dst))
67
+ FileUtils.mv(src, dst)
68
+ rescue SystemCallError
69
+ nil
70
+ end
71
+
72
+ def index_path
73
+ File.join(cache_dir, INDEX_BASENAME)
74
+ end
75
+
76
+ def load_index_yaml
77
+ return {} unless File.exist?(index_path)
78
+
79
+ YAML.safe_load(File.read(index_path), :permitted_classes => [], :aliases => false) || {}
80
+ rescue Psych::SyntaxError
81
+ {}
82
+ end
83
+
84
+ def update_index_yaml(id:, pretty:)
85
+ idx = load_index_yaml
86
+ return if idx[id] == pretty
87
+
88
+ FileUtils.mkdir_p(File.dirname(index_path))
89
+ tmp = "#{index_path}.tmp"
90
+ idx[id] = pretty
91
+ File.write(tmp, idx.to_yaml)
92
+ FileUtils.mv(tmp, index_path)
93
+ end
94
+
95
+ def sanitize_title(str)
96
+ Jekyll::Utils.slugify(str)
97
+ end
98
+
99
+ def sanitize_id(id)
100
+ id.delete("-")
101
+ end
102
+
103
+ def extract_title(metadata)
104
+ metadata.title
105
+ end
106
+ end
107
+ end