• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hexojs / hexo / 7992207506

21 Feb 2024 04:17PM UTC coverage: 99.53%. Remained the same
7992207506

push

github

web-flow
test: fix typos (#5426)

2300 of 2391 branches covered (96.19%)

9312 of 9356 relevant lines covered (99.53%)

52.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.32
/lib/plugins/processor/post.ts
1
import { toDate, timezone, isExcludedFile, isTmpFile, isHiddenFile, isMatch } from './common';
1✔
2
import Promise from 'bluebird';
1✔
3
import { parse as yfm } from 'hexo-front-matter';
1✔
4
import { extname, join } from 'path';
1✔
5
import { stat, listDir } from 'hexo-fs';
1✔
6
import { slugize, Pattern, Permalink } from 'hexo-util';
1✔
7
import { magenta } from 'picocolors';
1✔
8
import type { _File } from '../../box';
1✔
9
import type Hexo from '../../hexo';
1✔
10
import type { Stats } from 'fs';
1✔
11

1✔
12
const postDir = '_posts/';
1✔
13
const draftDir = '_drafts/';
1✔
14
let permalink;
1✔
15

1✔
16
const preservedKeys = {
1✔
17
  title: true,
1✔
18
  year: true,
1✔
19
  month: true,
1✔
20
  day: true,
1✔
21
  i_month: true,
1✔
22
  i_day: true,
1✔
23
  hash: true
1✔
24
};
1✔
25

1✔
26
export = (ctx: Hexo) => {
1✔
27
  return {
69✔
28
    pattern: new Pattern(path => {
69✔
29
      if (isTmpFile(path)) return;
41✔
30

39✔
31
      let result;
39✔
32

39✔
33
      if (path.startsWith(postDir)) {
41✔
34
        result = {
13✔
35
          published: true,
13✔
36
          path: path.substring(postDir.length)
13✔
37
        };
13✔
38
      } else if (path.startsWith(draftDir)) {
41✔
39
        result = {
2✔
40
          published: false,
2✔
41
          path: path.substring(draftDir.length)
2✔
42
        };
2✔
43
      }
2✔
44

39✔
45
      if (!result || isHiddenFile(result.path)) return;
41✔
46

11✔
47
      // checks only if there is a renderer for the file type or if is included in skip_render
11✔
48
      result.renderable = ctx.render.isRenderable(path) && !isMatch(path, ctx.config.skip_render);
41✔
49

41✔
50
      // if post_asset_folder is set, restrict renderable files to default file extension
41✔
51
      if (result.renderable && ctx.config.post_asset_folder) {
41✔
52
        result.renderable = (extname(ctx.config.new_post_name) === extname(path));
3✔
53
      }
3✔
54

11✔
55
      return result;
11✔
56
    }),
69✔
57

69✔
58
    process: function postProcessor(file) {
69✔
59
      if (file.params.renderable) {
45✔
60
        return processPost(ctx, file);
37✔
61
      } else if (ctx.config.post_asset_folder) {
45✔
62
        return processAsset(ctx, file);
7✔
63
      }
7✔
64
    }
45✔
65
  };
69✔
66
};
69✔
67

1✔
68
function processPost(ctx: Hexo, file: _File) {
37✔
69
  const Post = ctx.model('Post');
37✔
70
  const { path } = file.params;
37✔
71
  const doc = Post.findOne({source: file.path});
37✔
72
  const { config } = ctx;
37✔
73
  const { timezone: timezoneCfg } = config;
37✔
74
  const updated_option = config.updated_option;
37✔
75
  let categories, tags;
37✔
76

37✔
77
  if (file.type === 'skip' && doc) {
37✔
78
    return;
1✔
79
  }
1✔
80

36✔
81
  if (file.type === 'delete') {
37✔
82
    if (doc) {
3✔
83
      return doc.remove();
1✔
84
    }
1✔
85

2✔
86
    return;
2✔
87
  }
2✔
88

33✔
89
  return Promise.all([
33✔
90
    file.stat(),
33✔
91
    file.read()
33✔
92
  ]).spread((stats: Stats, content: string) => {
33✔
93
    const data = yfm(content);
33✔
94
    const info = parseFilename(config.new_post_name, path);
33✔
95
    const keys = Object.keys(info);
33✔
96

33✔
97
    data.source = file.path;
33✔
98
    data.raw = content;
33✔
99
    data.slug = info.title;
33✔
100

33✔
101
    if (file.params.published) {
33✔
102
      if (!Object.prototype.hasOwnProperty.call(data, 'published')) data.published = true;
32✔
103
    } else {
33✔
104
      data.published = false;
1✔
105
    }
1✔
106

33✔
107
    for (let i = 0, len = keys.length; i < len; i++) {
33✔
108
      const key = keys[i];
43✔
109
      if (!preservedKeys[key]) data[key] = info[key];
43✔
110
    }
43✔
111

33✔
112
    if (data.date) {
33✔
113
      data.date = toDate(data.date);
6✔
114
    } else if (info && info.year && (info.month || info.i_month) && (info.day || info.i_day)) {
33!
115
      data.date = new Date(
3✔
116
        info.year,
3✔
117
        parseInt(info.month || info.i_month, 10) - 1,
3!
118
        parseInt(info.day || info.i_day, 10)
3!
119
      );
3✔
120
    }
3✔
121

33✔
122
    if (data.date) {
33✔
123
      if (timezoneCfg) data.date = timezone(data.date, timezoneCfg);
8✔
124
    } else {
33✔
125
      data.date = stats.birthtime;
25✔
126
    }
25✔
127

33✔
128
    data.updated = toDate(data.updated);
33✔
129

33✔
130
    if (data.updated) {
33✔
131
      if (timezoneCfg) data.updated = timezone(data.updated, timezoneCfg);
3✔
132
    } else if (updated_option === 'date') {
33✔
133
      data.updated = data.date;
1✔
134
    } else if (updated_option === 'empty') {
30✔
135
      data.updated = undefined;
1✔
136
    } else {
29✔
137
      data.updated = stats.mtime;
28✔
138
    }
28✔
139

33✔
140
    if (data.category && !data.categories) {
33✔
141
      data.categories = data.category;
1✔
142
      data.category = undefined;
1✔
143
    }
1✔
144

33✔
145
    if (data.tag && !data.tags) {
33✔
146
      data.tags = data.tag;
1✔
147
      data.tag = undefined;
1✔
148
    }
1✔
149

33✔
150
    categories = data.categories || [];
33✔
151
    tags = data.tags || [];
33✔
152

33✔
153
    if (!Array.isArray(categories)) categories = [categories];
33✔
154
    if (!Array.isArray(tags)) tags = [tags];
33✔
155

33✔
156
    if (data.photo && !data.photos) {
33✔
157
      data.photos = data.photo;
1✔
158
      data.photo = undefined;
1✔
159
    }
1✔
160

33✔
161
    if (data.photos && !Array.isArray(data.photos)) {
33✔
162
      data.photos = [data.photos];
1✔
163
    }
1✔
164

33✔
165
    if (data.permalink) {
33✔
166
      data.__permalink = data.permalink;
1✔
167
      data.permalink = undefined;
1✔
168
    }
1✔
169

33✔
170
    if (doc) {
33✔
171
      if (file.type !== 'update') {
3✔
172
        ctx.log.warn(`Trying to "create" ${magenta(file.path)}, but the file already exists!`);
2✔
173
      }
2✔
174
      return doc.replace(data);
3✔
175
    }
3✔
176

30✔
177
    return Post.insert(data);
30✔
178
  }).then(doc => Promise.all([
33✔
179
    doc.setCategories(categories),
33✔
180
    doc.setTags(tags),
33✔
181
    scanAssetDir(ctx, doc)
33✔
182
  ]));
33✔
183
}
33✔
184

1✔
185
function parseFilename(config: string, path: string) {
33✔
186
  config = config.substring(0, config.length - extname(config).length);
33✔
187
  path = path.substring(0, path.length - extname(path).length);
33✔
188

33✔
189
  if (!permalink || permalink.rule !== config) {
33✔
190
    permalink = new Permalink(config, {
8✔
191
      segments: {
8✔
192
        year: /(\d{4})/,
8✔
193
        month: /(\d{2})/,
8✔
194
        day: /(\d{2})/,
8✔
195
        i_month: /(\d{1,2})/,
8✔
196
        i_day: /(\d{1,2})/,
8✔
197
        hash: /([0-9a-f]{12})/
8✔
198
      }
8✔
199
    });
8✔
200
  }
8✔
201

33✔
202
  const data = permalink.parse(path);
33✔
203

33✔
204
  if (data) {
33✔
205
    if (data.title !== undefined) {
32✔
206
      return data;
31✔
207
    }
31✔
208
    return Object.assign(data, {
1✔
209
      title: slugize(path)
1✔
210
    });
1✔
211
  }
1✔
212

1✔
213
  return {
1✔
214
    title: slugize(path)
1✔
215
  };
1✔
216
}
1✔
217

1✔
218
function scanAssetDir(ctx: Hexo, post) {
33✔
219
  if (!ctx.config.post_asset_folder) return;
33✔
220

7✔
221
  const assetDir = post.asset_dir;
7✔
222
  const baseDir = ctx.base_dir;
7✔
223
  const sourceDir = ctx.config.source_dir;
7✔
224
  const baseDirLength = baseDir.length;
7✔
225
  const sourceDirLength = sourceDir.length;
7✔
226
  const PostAsset = ctx.model('PostAsset');
7✔
227

7✔
228
  return stat(assetDir).then(stats => {
7✔
229
    if (!stats.isDirectory()) return [];
7!
230

7✔
231
    return listDir(assetDir);
7✔
232
  }).catch(err => {
7✔
233
    if (err && err.code === 'ENOENT') return [];
×
234
    throw err;
×
235
  }).filter(item => !isExcludedFile(item, ctx.config)).map(item => {
7✔
236
    const id = join(assetDir, item).substring(baseDirLength).replace(/\\/g, '/');
8✔
237
    const renderablePath = id.substring(sourceDirLength + 1);
8✔
238
    const asset = PostAsset.findById(id);
8✔
239

8✔
240
    if (shouldSkipAsset(ctx, post, asset)) return undefined;
8✔
241

5✔
242
    return PostAsset.save({
5✔
243
      _id: id,
5✔
244
      post: post._id,
5✔
245
      slug: item,
5✔
246
      modified: true,
5✔
247
      renderable: ctx.render.isRenderable(renderablePath) && !isMatch(renderablePath, ctx.config.skip_render)
8✔
248
    });
8✔
249
  });
7✔
250
}
7✔
251

1✔
252
function shouldSkipAsset(ctx: Hexo, post, asset) {
8✔
253
  if (!ctx._showDrafts()) {
8✔
254
    if (post.published === false && asset) {
7✔
255
      // delete existing draft assets if draft posts are hidden
1✔
256
      asset.remove();
1✔
257
    }
1✔
258
    if (post.published === false) {
7✔
259
      // skip draft assets if draft posts are hidden
3✔
260
      return true;
3✔
261
    }
3✔
262
  }
7✔
263

5✔
264
  return asset !== undefined; // skip already existing assets
5✔
265
}
5✔
266

1✔
267
function processAsset(ctx: Hexo, file: _File) {
7✔
268
  const PostAsset = ctx.model('PostAsset');
7✔
269
  const Post = ctx.model('Post');
7✔
270
  const id = file.source.substring(ctx.base_dir.length).replace(/\\/g, '/');
7✔
271
  const doc = PostAsset.findById(id);
7✔
272

7✔
273
  if (file.type === 'delete') {
7✔
274
    if (doc) {
2✔
275
      return doc.remove();
1✔
276
    }
1✔
277

1✔
278
    return;
1✔
279
  }
1✔
280

5✔
281
  // TODO: Better post searching
5✔
282
  const post = Post.toArray().find(post => file.source.startsWith(post.asset_dir));
5✔
283
  if (post != null && (post.published || ctx._showDrafts())) {
7!
284
    return PostAsset.save({
3✔
285
      _id: id,
3✔
286
      slug: file.source.substring(post.asset_dir.length),
3✔
287
      post: post._id,
3✔
288
      modified: file.type !== 'skip',
3✔
289
      renderable: file.params.renderable
3✔
290
    });
3✔
291
  }
3✔
292

2✔
293
  if (doc) {
7✔
294
    return doc.remove();
1✔
295
  }
1✔
296
}
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc