Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/models/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import warehouse from 'warehouse';
import type Hexo from '../hexo';
import { CodeSchema } from '../types';
import { join } from 'path';

export = (ctx: Hexo) => {
const Code = new warehouse.Schema<CodeSchema>({
_id: { type: String, required: true },
path: { type: String, required: true },
slug: { type: String, required: true },
modified: { type: Boolean, default: true },
content: { type: String, default: '' }
});

Code.virtual('source').get(function() {
return join(ctx.base_dir, this._id);
});

return Code;
};
1 change: 1 addition & 0 deletions lib/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { default as PostAsset } from './post_asset';
export { default as PostCategory } from './post_category';
export { default as PostTag } from './post_tag';
export { default as Tag } from './tag';
export { default as Code } from './code';
6 changes: 3 additions & 3 deletions lib/plugins/generator/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Promise from 'bluebird';
import { extname } from 'path';
import { magenta } from 'picocolors';
import type Hexo from '../../hexo';
import type { AssetSchema, BaseGeneratorReturn } from '../../types';
import type { AssetSchema, BaseGeneratorReturn, PostAssetSchema } from '../../types';
import type Document from 'warehouse/dist/document';

interface AssetData {
Expand All @@ -19,9 +19,9 @@ interface AssetGenerator extends BaseGeneratorReturn {
}

const process = (name: string, ctx: Hexo) => {
return Promise.filter(ctx.model(name).toArray(), (asset: Document<AssetSchema>) => exists(asset.source).tap(exist => {
return Promise.filter(ctx.model(name).toArray(), (asset: Document<AssetSchema> | Document<PostAssetSchema>) => exists(asset.source).tap(exist => {
if (!exist) return asset.remove();
})).map((asset: Document<AssetSchema>) => {
})).map((asset: Document<AssetSchema> | Document<PostAssetSchema>) => {
const { source } = asset;
let { path } = asset;
const data: AssetData = {
Expand Down
27 changes: 27 additions & 0 deletions lib/plugins/generator/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type Hexo from '../../hexo';
import Promise from 'bluebird';
import { exists } from 'hexo-fs';
import type { CodeSchema } from '../../types';
import type Document from 'warehouse/dist/document';

interface CodeData {
modified: boolean;
data: string;
}

function codeGenerator(this: Hexo): Promise<any[]> {
return Promise.filter(this.model('Code').toArray(), (code: Document<CodeSchema>) => exists(code.source).tap(exist => {
if (!exist) return code.remove();
})).map((code: Document<CodeSchema>) => {
const { path } = code;
const data: CodeData = {
modified: code.modified,
data: code.content
};

return { path, data };
});

}

export = codeGenerator;
1 change: 1 addition & 0 deletions lib/plugins/generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type Hexo from '../../hexo';
export = (ctx: Hexo) => {
const { generator } = ctx.extend;

generator.register('code', require('./code'));
generator.register('asset', require('./asset'));
generator.register('page', require('./page'));
generator.register('post', require('./post'));
Expand Down
3 changes: 3 additions & 0 deletions lib/plugins/processor/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import { PageSchema } from '../../types';
import SourceIdIndex from './source_id_index';

export = (ctx: Hexo) => {
let codeDir = ctx.config.code_dir;
if (!codeDir.endsWith('/')) codeDir += '/';
const pageSourceIndex = new SourceIdIndex(ctx.model('Page'));

return {
pattern: new Pattern(path => {
if (isExcludedFile(path, ctx.config)) return;
if (path.startsWith(codeDir)) return;

return {
renderable: ctx.render.isRenderable(path) && !isMatch(path, ctx.config.skip_render)
Expand Down
45 changes: 45 additions & 0 deletions lib/plugins/processor/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Pattern } from 'hexo-util';
import { relative } from 'path';
import { isExcludedFile } from './common';
import type Hexo from '../../hexo';
import type { _File } from '../../box';

export = (ctx: Hexo) => {
let codeDir = ctx.config.code_dir;
if (!codeDir.endsWith('/')) codeDir += '/';
return {
pattern: new Pattern(path => {
if (!path.startsWith(codeDir)) return false;
if (isExcludedFile(path, ctx.config)) return false;
return true;
}),
process: function codeProcessor(file: _File) {
const id = relative(ctx.base_dir, file.source).replace(/\\/g, '/');
const slug = file.path;
const Code = ctx.model('Code');
const doc = Code.findById(id);

if (file.type === 'delete') {
if (doc) {
return doc.remove();
}

return;
}

if (file.type === 'skip' && doc) {
return;
}

return file.read().then(content => {
return Code.save({
_id: id,
path: file.path,
slug,
modified: file.type !== 'skip',
content
});
});
}
};
};
1 change: 1 addition & 0 deletions lib/plugins/processor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export = (ctx: Hexo) => {
processor.register(obj.pattern, obj.process);
}

register('code');
register('asset');
register('data');
register('post');
Expand Down
4 changes: 2 additions & 2 deletions lib/plugins/tag/include_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export = (ctx: Hexo) => function includeCodeTag(args: string[]) {
const source = join(codeDir, path).replace(/\\/g, '/');

// Prevent path traversal: https://github.com/hexojs/hexo/issues/5250
const Page = ctx.model('Page');
const doc = Page.findOne({ source });
const Code = ctx.model('Code');
const doc = Code.findOne({ slug: source });
if (!doc) return;

let code = doc.content;
Expand Down
9 changes: 9 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,15 @@ export interface PageSchema extends BasePagePostSchema {
tag?: string;
}

export interface CodeSchema {
_id: string;
path: string;
slug: string;
modified: boolean;
content: string;
source: string;
}

export interface AssetSchema {
_id?: string;
path: string;
Expand Down
116 changes: 116 additions & 0 deletions test/scripts/generators/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { join } from 'path';
import { mkdirs, rmdir, unlink, writeFile } from 'hexo-fs';
import Hexo from '../../../lib/hexo';
import codeGenerator from '../../../lib/plugins/generator/code';
import defaults from '../../../lib/hexo/default_config';
import chai from 'chai';
const should = chai.should();
type CodeParams = Parameters<typeof codeGenerator>
type CodeReturn = ReturnType<typeof codeGenerator>

describe('code', () => {
const hexo = new Hexo(join(__dirname, 'code_test'), {silent: true});
const generator: (...args: CodeParams) => CodeReturn = codeGenerator.bind(hexo);
const Code = hexo.model('Code');
const codeDir = defaults.code_dir;

before(async () => {
await mkdirs(hexo.base_dir);
await hexo.init();
});

after(() => rmdir(hexo.base_dir));

it('renderable', async () => {
const path = 'test.j2';
const source = join(hexo.base_dir, defaults.source_dir, defaults.code_dir, path);
const content = '{{ 1 }}';

await Promise.all([
Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`,
content
}),
writeFile(source, content)
]);
const data = await generator();
const item = data.find(d => d.path === `${codeDir}/${path}`);
should.exist(item);
item.path.should.eql(`${codeDir}/${path}`);
item.data.modified.should.be.true;

const result = await item.data.data;
result.should.eql(content);

await Promise.all([
Code.removeById(`${defaults.source_dir}/${codeDir}/${path}`),
unlink(source)
]);
});

it('not renderable', async () => {
const path = 'test.txt';
const source = join(hexo.base_dir, defaults.source_dir, defaults.code_dir, path);
const content = 'test content';

await Promise.all([
Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`,
content
}),
writeFile(source, content)
]);
const data = await generator();
const item = data.find(d => d.path === `${codeDir}/${path}`);
should.exist(item);
item.path.should.eql(`${codeDir}/${path}`);
item.data.modified.should.be.true;

const result = await item.data.data;
result.should.eql(content);

await Promise.all([
Code.removeById(`${defaults.source_dir}/${codeDir}/${path}`),
unlink(source)
]);
});

it('remove codes which does not exist', async () => {
const path = 'test.js';

await Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`
});
await generator();
should.not.exist(Code.findById(`${defaults.source_dir}/${codeDir}/${path}`));
});

it('don\'t remove extension name', async () => {
const path = 'test.min.js';
const source = join(hexo.base_dir, defaults.source_dir, defaults.code_dir, path);

await Promise.all([
Code.insert({
_id: `${defaults.source_dir}/${codeDir}/${path}`,
slug: `${codeDir}/${path}`,
path: `${codeDir}/${path}`
}),
writeFile(source, '')
]);
const data = await generator();
const item = data.find(d => d.path === `${codeDir}/${path}`);
should.exist(item);
item.path.should.eql(`${codeDir}/${path}`);

await Promise.all([
Code.removeById(`${defaults.source_dir}/${codeDir}/${path}`),
unlink(source)
]);
});
});
59 changes: 59 additions & 0 deletions test/scripts/models/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { join } from 'path';
import Hexo from '../../../lib/hexo';

describe('Code', () => {
const hexo = new Hexo();
const Code = hexo.model('Code');

it('_id - required', async () => {
try {
await Code.insert({});
} catch (err) {
err.message.should.eql('ID is not defined');
}
});

it('path - required', async () => {
try {
await Code.insert({
_id: 'foo'
});
} catch (err) {
err.message.should.eql('`path` is required!');
}
});

it('slug - required', async () => {
try {
await Code.insert({
_id: 'foo',
path: 'bar'
});
} catch (err) {
err.message.should.eql('`slug` is required!');
}
});

it('default values', async () => {
const data = await Code.insert({
_id: 'foo',
path: 'bar',
slug: 'baz'
});
data.modified.should.be.true;
data.content.should.eql('');

Code.removeById(data._id);
});

it('source - virtual', async () => {
const data = await Code.insert({
_id: 'foo',
path: 'bar',
slug: 'baz'
});
data.source.should.eql(join(hexo.base_dir, data._id));

Code.removeById(data._id);
});
});
Loading
Loading