constructor(...args) { args = to.arguments({ str: '', lineno: '' }, ...args) to.extend(this, args) this.raw = this.str this.indent = to.indentLevel(this.str) }
setOptions(options) { options = to.arguments({ language: { prefix: '@', // header comment style // @note {10} only 1 of these can be used per file header: { start: '////', line: '///', end: '////', type: 'header' }, // body comment style body: { start: '', line: '///', end: '', type: 'body' }, // inline comment style inline: { start: '', line: '///#', end: '', type: 'inline' }, // this is used for any interpolations that might occur in annotations. // I don't see this needing to change but just incase I'm making it a setting. // @note {10} This setting is used to create a RegExp so certain characters need to be escaped interpolation: { start: '\\${', end: '}' }, }, type: undefined, blank_lines: 4, indent: true, annotations: {}, sort: [], log: logger }, arguments) let { annotations, type, log, ...rest } = options this.log = log this.options = rest this.api = new AnnotationApi({ annotations, type }) // this is used to pass to the annotations when they're called this.annotation_options = { log: this.log, options: this.options } const { language } = this.options this.comment_values = to.flatten([ ...to.values(language.header, '!type'), ...to.values(language.body, '!type'), ...to.values(language.inline, '!type') ]) this.comment_values = to.unique(this.comment_values).filter(Boolean) { let { alias, parse } = this.api.annotations parse = to.keys(parse) const reverse_alias_list = to.reduce(alias, (previous, { key, value }) => { value = value .filter((_alias) => !is.in(parse, _alias)) .reduce((a, b) => to.extend(a, { [b]: key }), {}) return to.extend(previous, value) }, {}) const regex = new RegExp(`^\s*${language.prefix}(?:(${to.keys(reverse_alias_list).join('|')})|(${parse.join('|')}))\\b\\s*`) const available = to.unique(to.reduce(this.api.annotations, (previous, { value }) => previous.concat(to.keys(value)), [])) this.annotations_list = { available, reverse_alias_list, regex } } this.options.order = sort(this.options.sort, { extras: this.annotations_list.available, amount: 3 }) this.options.order.push('inline') // this is for the inline part of the annotation }
setOptions(options) { // eslint-disable-line options = to.arguments({ content: '', lineno: 0, comment: this.options.comment || { start: '', line: '///', end: '', type: undefined }, // the default comment style to look for blank_lines: this.options.blank_lines || default_options.blank_lines, // determins if all the extra line info will be returned or if it will just return strings verbose: !is.undefined(this.options.verbose) ? this.options.verbose : false, // determins if the comment should be stripped from the line strip: !is.undefined(this.options.strip) ? this.options.strip : false, // if true this option will only return the first token restrict: !is.undefined(this.options.restrict) ? this.options.restrict : false, // determins if the code below should stop parsing if the indent level is less than the starting indent level indent: !is.undefined(this.options.indent) ? this.options.indent : true, offset: 0, }, arguments) { // parse the comment to ensure the settings are valid, and attempt to update them // to be valid if they aren't valid let { start, line, single, end, type } = options.comment single = single || line // this ensures there aren't any errors while looking comment lines // because `''` will always have an index of `0` if (single === '') { single = undefined } if (is.any.in([ single, '' ], start, end)) { start = end = undefined } if (!single && is.any.undefined(start, end)) { throw new Error("You must set the start and end comment style if you don't specify a single comment") } else if (!start && !end && !single) { throw new Error('You must set the single comment or the start and end comment') } else if (is.all.existy(single, start, end) && (start.length <= single.length || start.end <= single.length)) { throw new Error('The start and end comments must be longer than the single comment') } options.comment = { start, single, end, type } this.is_multi = is.all.truthy(start, end) this.is_same_multi = this.is_multi && start === end } { // set the lineno to start with const { i, index, lineno, start_at, ...rest } = options this.lineno = i || index || start_at || lineno || 0 options = rest } let stash { // set the string to use let { str, string, source, code, content, contents, ...rest } = options stash = str || string || source || code || content || contents || '' options = rest } // update the options this.options = options // holds the parsed tokens this.tokens = [] this.current_blank_lines = 0 // update the stash this.stash = this.getStash(stash) // update the iterator to use this.iterator = to.entries(this.stash, this.lineno) }
run(type, ...options) { { const base = { contents: [], start: -1, end: -1 } options = to.arguments({ annotation: { name: '', alias: '', ...base }, file: { path: '', name: '', type: '', ...base }, comment: { ...base }, code: { ...base }, }, ...options) } // /// @name add // /// @page annotation // /// @description Allows you to add a different annotation from within a annotation // /// @arg {string} name - the name of the annotation you want to add // /// @arg {string} str - information that is passed to the annotation // const add = (name, contents) => { // contents = to.normalize(contents) // return this.run({ // annotation: { // name, // alias: is.in(this.annotations.alias, name) ? this.annotations.alias[name] : [], // line: to.normalize(contents[0]), // contents, // start: null, // end: null // }, // annotation_types, // ...block, // log // }) // } { const list = this.annotations_list const { annotation, file } = options const fn = (list[file.type] || {})[annotation.name] let default_fn = (list.default || {})[annotation.name] if (fn && default_fn && (default_fn = default_fn[type])) { options.default = () => default_fn.call(options) } } let details = options try { const fn = this.getAnnotationFn(options.annotation.name, type) if (type === 'parse') { details = copyInvisible(options.annotation, options) } else if (type === 'resolve') { details = copyInvisible(options.parsed[options.annotation.name], options) } let result try { result = is.function(fn) ? fn.call(details) : fn } catch (e) { throw e } if (type === 'parse' && !is.any.undefined(details, result)) { // copy the details that were used to call the parse function command onto the result // as `details` this gives the resolve function the ability to access all of the information if needed if (is.array(result) && result.length === 1) { result = to.map(result, (obj) => copyInvisible(obj, { details })) } result = copyInvisible(result, { details }) } return result } catch (e) { throw e } function copyInvisible(obj, value) { let _type = to.type(obj) switch (_type) { case 'array': case 'object': break default: let types = { String, Number, Boolean } let Type = types[_type.charAt(0).toUpperCase() + _type.slice(1)] obj = new Type(obj) } // loop over the data and set them to be getters on the // passed object. This makes them available but they don't show // up in the console and clutter up all the things for (var item in value) { if (value.hasOwnProperty(item)) { Object.defineProperty(obj, item, { value: value[item] }) } } // set the prototype to the the passed data Object.defineProperty(obj, 'prototype', { value }) return obj } }