Skip to content

CustomTemplate

sunben edited this page Jul 13, 2019 · 2 revisions

自定义模板

配置流程

本扩展定义了一个配置文件,该配置文件定义了,需要询问用户输入那些参数、每个参数输入框展示什么样的UI、参数的建议值是什么、是否渲染注释、注释配置如何等等。

如果想要自定义模板需要,克隆下来本扩展的 template 目录到本地。

git clone https://github.com/rectcircle/new-file-by-type.git

或者 点此 下载,并解压。

然后指定配置

{
  "new-file-by-type.global.templatePath": "clone下来的项目的template目录"
}

然后在template目录下添加模板的配置文件。模板目录的组织结构和配置文件语法,参见下文。

最后执行 本扩展的 重新加载模板 命令

模板目录组织结构

子目录

模板目录之下有4个子目录

  • new —— 文件模板存放位置
  • search —— 网络搜索相关的配置
  • translate —— 翻译配置
  • i18n —— 全局国际化目录

名字空间

模板目录的每个子孙目录(i18n目录的除外),都存在一个名字空间,名字空间以是相对于模板根目录的相对路径(使用 . 分割)。例如 ${template}/new/java 的 名字空间为 new.java${template}/new/java/common/class 的名字空间为 new.java.common.class

目录内容

模板目录的每个子孙目录有两种类型

  • 模板集合 目录:含有子目录的目录(i18n目录除外)
  • 模板定义 目录:只含有文件的目录

模板集合 是包含其他 模板集合模板定义 的目录。模板定义 是单纯的模板配置文件所在的目录。

模板集合模板定义目录都允许包含:

  • 名为 config.jsonc 的配置文件
  • 名为 i18n 的目录
  • 其他在 config.jsonc 中引用的文件

重要提示:子目录会继承父目录的配置,同时子目录可以覆写父目录的配置

配置文件

在 template 目录及子孙目录中名为 config.jsonc 的文件是本扩展的配置文件。配置文件格式是名为 json5 扩展的 JSON 格式,支持///**/注释。

配置文件支持嵌入JavaScript语法Json 文件中所有的 value 允许使用 {{ Statement }} 书写 JavaScript 语句,在读取配置文件时,会在适当的时机使用 JavaScript eval 函数执行 JavaScript 语句以获取结果,同时在还提供一些常用的内置函数,还允许用户自定义函数,这提供了巨大的灵活性。

更多详细的配置参见 配置文件和模板文件支持的 模板引擎

小技巧

本项目提供了配置文件的 json-schema。在自定义模板时,可以参考或者配置到VSCode中以进行提示

{
    "json.schemas": [
        {
            "fileMatch": ["/template/config.jsonc", "/template/**/config.jsonc"],
            "url": "/src/config.schema.json"
        },
    ],
}

例子

{
  "name": "默认从key为`文件夹名.name`的i18n文件中读取",
  "description": "默认从key为`文件夹名.description`的i18n文件中读取",
  "version": "0.0.1",
  "suffix": "",
  "flat": false,
  "renderComment": true,
  "indent": "{{defaultConf.indent}}",
  "user": "{{defaultConf.user}}",
  "author": [
    {
      "name": "Rectcircle",
      "homePage": "https://www.rectcircle.cn"
    }
  ],
  "placeHolder": "{{i18n('templateSelectPlaceHolder')}}",
  "showHidden": false,
  "targets": [{
    "filepath": "{{ path.resolve(projectFolder, inputs.srcPath, inputs.filename + ( suffix ?  '.' + suffix : ''))}}",
    "tplpath": "tpl",
    "saveType": "override"
  }],
  "inputs": [
    {
      "type": "path",
      "name": "srcPath",
      "prompt": "{{i18n('inputs.srcPath.prompt')}}",
      "placeHolder": "{{i18n('inputs.srcPath.placeHolder')}}",
      "value": "{{helper.activeDirectoryRelativeBasePath(projectFolder)}}",
      "suggest": {
        "value": "src"
      }
    },
    {
      "type": "text",
      "name": "filename",
      "prompt": "{{i18n('inputs.filename.prompt')}}",
      "placeHolder": "{{i18n('inputs.filename.placeHolder')}}",
      "suggest": {
        "selected": true,
        "value": "main"
      }
    }
  ],
  "comment": {
    "copyright": "Copyright (c) {{year}}, {{user}}. All rights reserved.",
    "dateFormat": "YYYY-MM-DD",
    "startLine": "/**",
    "lineHeader": " * ",
    "endLine": " */",
    "items": [
      "{{comment.copyright}}",
      "",
      "@author {{user}}",
      "@date {{date}}",
      "@version {{version}}"
    ]
  },
  "match": {
    "always": true
  }
}

基本配置项

key type 默认值 描述
name string "{{ i18n(`${dir}.name`) }}" template or type name(模板或模板目录名)
description string "{{ i18n(`${dir}.descriptin`) }}" template or type description(模板或模板目录描述)
weight number 100 Template sort weight, The value is small in front(排序权重, 小的在前面)
version string "0.0.1" default version will use in comment(默认版本,将会渲染在模板配置中)
flat boolean false subtype flat display(是否展平)
renderComment boolean Whether to render comments(是否渲染注释)
indent number "{{defaultConf.indent}}" file indent type: 0 - use Tab, non-zero - use indent number space(缩进类型0-tab, 非0-空格)
user string "{{defaultConf.user}}" coder name can use in tpl (用户名,将会渲染在注释中)
author array template author(模板作者,支持多个)
author[].name string author name(模板作者名)
author[].email string author email(模板作者email)
author[].homePage string author home page(模板作者个人主页)
suffix string "" file suffix(将要创建的文件后缀)
showHidden boolean false show hidden directory(目录选择器是否显示隐藏文件)
placeHolder string "{{i18n('templateSelectPlaceHolder')}}" template select view placeHolder text(模板选择器提示文本)
comment object comment config(注释相关配置)
comment.copyright string "Copyright (c) {{year}}, {{user}}. All rights reserved." copyright string
comment.dateFormat string "YYYY-MM-DD" code comment date format(日期格式化)
comment.startLine string "/**" comment first line string(注释起始内容)
comment.lineHeader string "* " expect first and last line, comment every line header string(注释每一行起始字符串)
comment.endLine string " */" comment last line string(注释每一行结束字符串)
comment.items array comment items (注释需要渲染的每一项)
match object show when this condition is matched(根据条件选择是否显示该模板)
match.always boolean true Whether it always matches(是否总是启用该模板)
match.workspaceFolderGlobs array workspace folder glob match(工作空间文件glob匹配表达式)

inputs 用户输入定义

该配置项定义了用户输入项(或者是配置项),是核心配置。类型为 array

目前支持4中 inputs[].type 输入类型分别是:

  • path 文件路径
  • text 文本
  • select 选择器
  • search 搜索框

用户每完成一个输入,模板引擎内的内置变量 inputs 对象将添加一个 keyinputs[].name 的 值

全部配置如下

key type 默认值 描述
inputs[].type enum("path", "text", "select", "search") 无,必填 input type(输入类型)
inputs[].name string 无,必填 input name(变量名)
inputs[].value string 无,选填 if set value, will not show the input windows(如果配置了该值,将不会展示输入框,该输入将作为配置项)
inputs[].prompt string "{{i18n(`inputs.${name}.prompt`)}}" prompt(提示)
inputs[].placeHolder string "{{i18n(`inputs.${name}.placeHolder`)}}" placeHolder(输入框内提示)
inputs[].items `array promise<array> ((keyword: string) => array
inputs[].suggest object 建议值
inputs[].suggest.selected boolean true 是否选中建议值
inputs[].suggest.value `array string` 无,选填
inputs[].checkRules `CheckRule array` 无,选填
inputs[].option object 无,选填 其他选项
inputs[].option.parentDirectoryText string "{{i18n(`inputs.${name}.parentDirectoryText`)}}" 父目录展示名
inputs[].option.confirmText string "{{i18n(`inputs.${name}.confirmText`)}}" 确认项文本
inputs[].option.pathSeparator string "{{ path.sep }}" path 分隔符,Linux为/ windows为\\
inputs[].option.suggestText string "{{i18n(`inputs.${name}.suggestText`)}}" 建议值标签
inputs[].option.directoryText string "{{i18n(`inputs.${name}.directoryText`)}}" 目录项标签
inputs[].option.fileText string "{{i18n(`inputs.${name}.fileText`)}}" 文件项标签
inputs[].option.confirmDetailText string "{{i18n(`inputs.${name}.confirmDetailText`)}}" 确认项detail文本
inputs[].option.currentDirectoryText string "{{i18n(`inputs.${name}.currentDirectoryText`)}}" 当前目录文本
inputs[].option.allowNoExist boolean true 是否允许选择不存在的路径
inputs[].option.resultExistAndTypeErrorText string "{{i18n(`inputs.${name}.resultExistAndTypeErrorText`)}}" 选择内容类型与要求类型不匹配错误提示文本
inputs[].option.returnType enum("file" , "directory" , "all") "directory" 选择目标的类型
inputs[].option.basePath string {{projectFolder}} 在哪个目录下选择子目录
inputs[].option.canSelectMany boolean false 是否选择多个
inputs[].option.canSelectEmpty boolean false 多选是否可以选择0个
inputs[].option.multiConfirmText string "{{i18n(`inputs.${name}.multiConfirmText`)}}" 未使用
inputs[].option.multiConfirmDetailText string "{{i18n(`inputs.${name}.multiConfirmDetailText`)}}" 多选确认Detail文本
inputs[].option.multiSelectCancelText string "{{i18n(`inputs.${name}.multiSelectCancelText`)}}" 多选取消文本

类型定义如下

type CheckRule = (value: string) => string | undefined | Promise<string | undefined>;
type Item = string | {
              label: string;
              description?: string;
              detail?: string;
              picked?: boolean;
              alwaysShow?: boolean;
              value: any;
            }

path 路径选择器

path 类型的配置

key type 默认值 描述
inputs[].type enum("path", "text", "select", "search") 无,必填 input type(输入类型)
inputs[].name string 无,必填 input name(变量名)
inputs[].value string 无,选填 if set value, will not show the input windows(如果配置了该值,将不会展示输入框,该输入将作为配置项)
inputs[].prompt string "{{i18n(`inputs.${name}.prompt`)}}" prompt(提示)
inputs[].placeHolder string "{{i18n(`inputs.${name}.placeHolder`)}}" placeHolder(输入框内提示)
inputs[].suggest object 建议值
inputs[].suggest.value `array string` 无,选填
inputs[].checkRules `CheckRule array` 无,选填
inputs[].option object 无,选填 其他选项
inputs[].option.parentDirectoryText string "{{i18n(`inputs.${name}.parentDirectoryText`)}}" 父目录展示名
inputs[].option.confirmText string "{{i18n(`inputs.${name}.confirmText`)}}" 确认项文本
inputs[].option.pathSeparator string "{{ path.sep }}" path 分隔符,Linux为/ windows为\\
inputs[].option.suggestText string "{{i18n(`inputs.${name}.suggestText`)}}" 建议值标签
inputs[].option.directoryText string "{{i18n(`inputs.${name}.directoryText`)}}" 目录项标签
inputs[].option.fileText string "{{i18n(`inputs.${name}.fileText`)}}" 文件项标签
inputs[].option.confirmDetailText string "{{i18n(`inputs.${name}.confirmDetailText`)}}" 确认项detail文本
inputs[].option.currentDirectoryText string "{{i18n(`inputs.${name}.currentDirectoryText`)}}" 当前目录文本
inputs[].option.allowNoExist boolean true 是否允许选择不存在的路径
inputs[].option.resultExistAndTypeErrorText string "{{i18n(`inputs.${name}.resultExistAndTypeErrorText`)}}" 选择内容类型与要求类型不匹配错误提示文本
inputs[].option.returnType enum("file" , "directory" , "all") "directory" 选择目标的类型
inputs[].option.basePath string {{projectFolder}} 在哪个目录下选择子目录
inputs[].option.canSelectMany boolean false 是否选择多个
inputs[].option.canSelectEmpty boolean false (多选有效)多选是否可以选择0个
inputs[].option.multiConfirmText string "{{i18n(`inputs.${name}.multiConfirmText`)}}" 未使用
inputs[].option.multiConfirmDetailText string "{{i18n(`inputs.${name}.multiConfirmDetailText`)}}" 多选确认Detail文本
inputs[].option.multiSelectCancelText string "{{i18n(`inputs.${name}.multiSelectCancelText`)}}" 多选取消文本

text

key type 默认值 描述
inputs[].type enum("path", "text", "select", "search") 无,必填 input type(输入类型)
inputs[].name string 无,必填 input name(变量名)
inputs[].value string 无,选填 if set value, will not show the input windows(如果配置了该值,将不会展示输入框,该输入将作为配置项)
inputs[].prompt string "{{i18n(`inputs.${name}.prompt`)}}" prompt(提示)
inputs[].placeHolder string "{{i18n(`inputs.${name}.placeHolder`)}}" placeHolder(输入框内提示)
inputs[].suggest object 建议值
inputs[].suggest.selected boolean true 是否选中建议值,如果多个,选第一个
inputs[].suggest.value `array string` 无,选填
inputs[].checkRules `CheckRule array` 无,选填

select

key type 默认值 描述
inputs[].type enum("path", "text", "select", "search") 无,必填 input type(输入类型)
inputs[].name string 无,必填 input name(变量名)
inputs[].value string 无,选填 if set value, will not show the input windows(如果配置了该值,将不会展示输入框,该输入将作为配置项)
inputs[].placeHolder string "{{i18n(`inputs.${name}.placeHolder`)}}" placeHolder(输入框内提示)
inputs[].items `array promise<array> ` 无,选填
inputs[].checkRules `CheckRule array` 无,选填
inputs[].option object 无,选填 其他选项
inputs[].option.canSelectMany boolean false 是否选择多个

search

key type 默认值 描述
inputs[].type enum("path", "text", "select", "search") 无,必填 input type(输入类型)
inputs[].name string 无,必填 input name(变量名)
inputs[].value string 无,选填 if set value, will not show the input windows(如果配置了该值,将不会展示输入框,该输入将作为配置项)
inputs[].prompt string "{{i18n(`inputs.${name}.prompt`)}}" prompt(提示)
inputs[].placeHolder string "{{i18n(`inputs.${name}.placeHolder`)}}" placeHolder(输入框内提示)
inputs[].items `(keyword: string) => array promise<array>` 无,选填
inputs[].suggest object 建议值
inputs[].suggest.selected boolean true 是否选中建议值(由于vscode不支持所以暂时无效)
inputs[].suggest.value `array string>` 无,选填
inputs[].option object 无,选填 其他选项
inputs[].option.canSelectMany boolean false 是否选择多个

targets 目标

本扩展不仅支持将最终渲染的模板写入文件,还支持写入剪切板,打开浏览器等功能。本扩展支持多个输出目标

key type 默认值 描述
targets[].targetType emun("file", "clipboard", "command", "browser") "file" 目标输出类型
targets[].filepath string "{{ path.resolve(projectFolder, inputs.srcPath, inputs.filename + ( suffix ? '.' + suffix : ''))}}" target file path to generate(文件写入的目标路径)
targets[].tplpath string "tpl" template file path(模板文件的路径)
targets[].tplcontent string "tpl" template content, Priority is higher than tplpath(模板内容,比tplpath有更高的优先级)
targets[].saveType enum("override", "append", "insert") "override" tpl save type (targetType = "file")(文件方式时,保存方式)

注意:

  • tplcontenttplpath 有更高的优先级
  • tplcontenttplpath 指向的文件同样支持模板语法 {{}}
  • targetType == 'file' 时,filepathsaveType 配置有效
  • targetType == 'clipboard' 时,模板渲染结果将写入剪切板
  • targetType == 'command' 时,模板渲染结果作为命令在shell中执行
  • targetType == 'browser' 时,模板渲染结果作为url打开浏览器

declaration 自定义声明文件

{
  "declaration" : "declaration.js"
}

自定义声明文件路径,只支持相对路径,相对于当前配置文件所在目录。声明文件支持JavaScript语法(NodeJs 10.x)。

不同于普通的NodeJs模块,声明文件不支持export等模块化操作,如果想在配置文件和模板中使用。请将变量或函数赋值为 declaration

为了方便调试,本扩展提供了模板引擎上下文文件 context.js,使用特殊的注释标记 /*<...>*/ /*<.../>*/ 可以在解析该文件时,自动忽略测试内容。

例子 template/new/js/declaration.js

// 使用  /*<...>*/这里是包裹的代码 /*<.../>*/ 包裹的代码,eval执行之前会被删除,用于测试时使用
/*<...>*/
const {
  path,
  fs,
  os,
  axios,
  moment,
  cheerio,
  happyCoding,
  happyCodingString,
  language,
  openedFilePaths,
  defaultConf,
  encoding,
  useActive,
  activeDirectory,
  now,
  year,
  name,
  description,
  suffix,
  flat,
  indent,
  user,
  placeHolder,
  targets,
  version,
  comment,
  declaration,
  customize,
  Date,
  projectFolder,
  commentOutput,
  inputs,
  helper,
  i18n,
  checkRules,
} = require('../../../context');
/*<.../>*/

// 以下是自定义函数的正文, 请将的声明的函数或者变量放置到 declaration 中
/**
 * 根据 targetPath(当前文件) 和 importPaths (导入文件绝对路径),解析文件名和相对路径
 * @param {string} targetPath 当前文件
 * @param {string[]} 要导入的文件
 * @param {(name: string, relative: string, extname: string) => string} 提取出数据如何使用的函数
 */
declaration['nodeImports'] = function (targetPath, importPaths, toString) {
  let result = [];
  let targetDirPath = path.dirname(targetPath);
  for (let p of importPaths) {
    let importFullPath = path.resolve(projectFolder, p);
    let filename = path.basename(p);
    let extname = path.extname(filename);
    filename = filename.replace(extname, '');
    let relative = path.relative(targetDirPath, importFullPath);
    if (!relative.startsWith('.')) {
      relative = './' + relative;
    }
    relative = relative.replace(extname, '');
    result.push(toString(filename, relative, extname));
  }
  if (result.length === 0) {
    return '';
  }
  return result.join('\n') + '\n';
};

/*<...>*/
async function test() {
  const result = await declaration['nodeImports'](
    '/a/b/project/src/test/test.js',
    ['/a/b/project/src/main.js', '/a/b/project/src/lib/util.js'],
    (name, relative) => `import * as ${name} from '${relative}'`);
  console.log(result)
}

test();
/*<.../>*/

I18n

template 任意子孙目录下均允许存在i18n目录,该目录下包含以小写国家码为名字的json文件(注意不是json5格式,是标准json格式)。

json文件定义的键值对,通过 i18n 函数可以根据VSCode选择的语言选取不同文件中的值作为返回值。如果不存在将返回警告字符串,而不会抛出异常

模板引擎

为了最大的灵活性,本扩展实现了一个简易的模板引擎。

该模板引擎JavaScript基于 函数闭包eval 实现全功能的JavaScript的模板支持。

模板引擎提供了很多内置的模块、变量和函数,均可通过 {{}} 访问。

详细定义可以参见 context.js

使用范围

以下两个地方的使用 {{}} 包裹

  • config.jsonc
  • config.jsonc 配置的 targets[].tplpath 指向的模板文件

注意在 config.jsonc 中使用

  • "key": "这是字符串的其他内容 {{ source }}"eval(source) 并转换为字符串 并替换{{ source }}
  • "key": "{{ source }}eval(source) 并不会转换为字符串,保留原有类型

此外针对 config.jsonc 的配置 declaration 指向的声明文件

  • 将可以直接使用模板引擎内置的变量、函数和模块
  • 不支持模块化语法
  • 最佳实践是将 要在配置或模板使用的函数或变量赋值给内置的 declaration 变量

内置模块

  • path
  • fs
  • os
  • axios http客户端
  • moment 日期时间库
  • cheerio nodejs dom操纵

内置变量和函数

  • happyCoding,
  • happyCodingString,
  • language,
  • openedFilePaths,
  • defaultConf, 将根据用户系统状态获取
  • encoding,
  • useActive,
  • activeDirectory,
  • now,
  • year,
  • name,
  • description,
  • suffix,
  • flat,
  • indent,
  • user,
  • placeHolder,
  • targets,
  • version,
  • comment,
  • declaration,
  • customize,
  • date,
  • projectFolder,
  • commentOutput,
  • inputs,
  • helper,
  • i18n,
  • checkRules,