UNPKG

compound-ex4

Version:

Compound-ex4 - MVC framework for NodeJS (ExpressJs 4 version), fork compoundjs(https://github.com/1602/compound)

968 lines (822 loc) 30.5 kB
events = require './events' util = require './util' {YAMLError} = require './errors' class @EmitterError extends YAMLError ### Emitter expects events obeying the following grammar: stream ::= STREAM-START document* STREAM-END document ::= DOCUMENT-START node DOCUMENT-END node ::= SCALA | sequence | mapping sequence ::= SEQUENCE-START node* SEQUENCE-END mapping ::= MAPPING-START (node node)* MAPPING-END ### class @Emitter C_WHITESPACE = '\0 \t\r\n\x85\u2028\u2029' DEFAULT_TAG_PREFIXES = '!' : '!' 'tag:yaml.org,2002:': '!!' ESCAPE_REPLACEMENTS = '\0' : '0' '\x07' : 'a' '\x08' : 'b' '\x09' : 't' '\x0A' : 'n' '\x0B' : 'v' '\x0C' : 'f' '\x0D' : 'r' '\x1B' : 'e' '"' : '"' '\\' : '\\' '\x85' : 'N' '\xA0' : '_' '\u2028': 'L' '\u2029': 'P' constructor: (@stream, options) -> # Encoding can be overriden by STREAM-START @encoding = null # Emitter is a state machine with a stack of states to handle nested structures. @states = [] @state = @expect_stream_start # Current event and the event queue @events = [] @event = null # The current indentation level and the stack of previous indents. @indents = [] @indent = null # Flow level. @flow_level = 0 # Contexts. @root_context = false @sequence_context = false @mapping_context = false @simple_key_context = false # Characteristics of the last emitted character: # - current position. # - is it a whitespace? # - is it an indentation character (indentation space, '-', '?', or ':')? @line = 0 @column = 0 @whitespace = true @indentation = true # Whether the document requires an explicit document indicator. @open_ended = false # Formatting details { @canonical, @allow_unicode } = options @canonical ?= false @allow_unicode ?= true @best_indent = if 1 < options.indent and options.indent < 10 then options.indent else 2 @best_width = if options.width > @indent * 2 then options.width else 80 @best_line_break = if options.line_break in [ '\r', '\n', '\r\n' ] then options.line_break else '\n' # Tag prefixes. @tag_prefixes = null # Prepared anchor and tag @prepared_anchor = null @prepared_tag = null # Scalar analysis and style. @analysis = null @style = null ### Reset the state attributes (to clear self-references) ### dispose: -> @states = [] @state = null emit: (event) -> @events.push event until @need_more_events() @event = @events.shift() @state() @event = null ### In some cases, we wait for a few next events before emitting. ### need_more_events: -> return true if @events.length is 0 event = @events[0] if event instanceof events.DocumentStartEvent then @need_events 1 else if event instanceof events.SequenceStartEvent then @need_events 2 else if event instanceof events.MappingStartEvent then @need_events 3 else false need_events: (count) -> level = 0 for event in @events[1..] if event instanceof events.DocumentStartEvent or event instanceof events.CollectionStartEvent level++ else if event instanceof events.DocumentEndEvent or event instanceof events.CollectionEndEvent level-- else if event instanceof events.StreamEndEvent level = -1 return false if level < 0 @events.length < count + 1 increase_indent: (options = {}) -> @indents.push @indent if not @indent? @indent = if options.flow then @best_indent else 0 else if not options.indentless @indent += @best_indent # Stream states expect_stream_start: -> if @event instanceof events.StreamStartEvent @encoding = @event.encoding if @event.encoding and 'encoding' not of @stream @write_stream_start() @state = @expect_first_document_start else @error 'expected StreamStartEvent, but got', @event expect_nothing: -> @error 'expected nothing, but got', @event # Document states expect_first_document_start: -> @expect_document_start true expect_document_start: (first = false) -> if @event instanceof events.DocumentStartEvent if (@event.version or @event.tags) and @open_ended @write_indicator '...', true @write_indent() if @event.version @write_version_directive @prepare_version @event.version @tag_prefixes = util.clone DEFAULT_TAG_PREFIXES if @event.tags for handle in (k for own k of @event.tags).sort() prefix = @event.tags[handle] @tag_prefixes[prefix] = handle @write_tag_directive @prepare_tag_handle(handle), @prepare_tag_prefix(prefix) explicit = not first or @event.explicit or @canonical or @event.version or @event.tags or @check_empty_document() if explicit @write_indent() @write_indicator '---', true @write_indent() if @canonical @state = @expect_document_root else if @event instanceof events.StreamEndEvent if @open_ended @write_indicator '...', true @write_indent() @write_stream_end() @state = @expect_nothing else @error 'expected DocumentStartEvent, but got', @event expect_document_end: -> if @event instanceof events.DocumentEndEvent @write_indent() if @event.explicit @write_indicator '...', true @write_indent() @flush_stream() @state = @expect_document_start else @error 'expected DocumentEndEvent, but got', @event expect_document_root: -> @states.push @expect_document_end @expect_node root: true # Node states expect_node: (expect = {}) -> @root_context = !!expect.root @sequence_context = !!expect.sequence @mapping_context = !!expect.mapping @simple_key_context = !!expect.simple_key if @event instanceof events.AliasEvent @expect_alias() else if @event instanceof events.ScalarEvent or @event instanceof events.CollectionStartEvent @process_anchor '&' @process_tag() if @event instanceof events.ScalarEvent @expect_scalar() else if @event instanceof events.SequenceStartEvent if @flow_level or @canonical or @event.flow_style or @check_empty_sequence() @expect_flow_sequence() else @expect_block_sequence() else if @event instanceof events.MappingStartEvent if @flow_level or @canonical or @event.flow_style or @check_empty_mapping() @expect_flow_mapping() else @expect_block_mapping() else @error 'expected NodeEvent, but got', @event expect_alias: -> @error 'anchor is not specified for alias' unless @event.anchor @process_anchor '*' @state = @states.pop() expect_scalar: -> @increase_indent flow: true @process_scalar() @indent = @indents.pop() @state = @states.pop() # Flow sequence states expect_flow_sequence: -> @write_indicator '[', true, whitespace: true @flow_level++ @increase_indent flow: true @state = @expect_first_flow_sequence_item expect_first_flow_sequence_item: -> if @event instanceof events.SequenceEndEvent @indent = @indents.pop() @flow_level-- @write_indicator ']', false @state = @states.pop() else @write_indent() if @canonical or @column > @best_width @states.push @expect_flow_sequence_item @expect_node sequence: true expect_flow_sequence_item: -> if @event instanceof events.SequenceEndEvent @indent = @indents.pop() @flow_level-- if @canonical @write_indicator ',', false @write_indent() @write_indicator ']', false @state = @states.pop() else @write_indicator ',', false @write_indent() if @canonical or @column > @best_width @states.push @expect_flow_sequence_item @expect_node sequence: true # Flow mapping states expect_flow_mapping: -> @write_indicator '{', true, whitespace: true @flow_level++ @increase_indent flow: true @state = @expect_first_flow_mapping_key expect_first_flow_mapping_key: -> if @event instanceof events.MappingEndEvent @indent = @indents.pop() @flow_level-- @write_indicator '}', false @state = @states.pop() else @write_indent() if @canonical or @column > @best_width if not @canonical and @check_simple_key() @states.push @expect_flow_mapping_simple_value @expect_node mapping: true, simple_key: true else @write_indicator '?', true @states.push @expect_flow_mapping_value @expect_node mapping: true expect_flow_mapping_key: -> if @event instanceof events.MappingEndEvent @indent = @indents.pop() @flow_level-- if @canonical @write_indicator ',', false @write_indent() @write_indicator '}', false @state = @states.pop() else @write_indicator ',', false @write_indent() if @canonical or @column > @best_width if not @canonical and @check_simple_key() @states.push @expect_flow_mapping_simple_value @expect_node mapping: true, simple_key: true else @write_indicator '?', true @states.push @expect_flow_mapping_value @expect_node mapping: true expect_flow_mapping_simple_value: -> @write_indicator ':', false @states.push @expect_flow_mapping_key @expect_node mapping: true expect_flow_mapping_value: -> @write_indent() if @canonical or @column > @best_width @write_indicator ':', true @states.push @expect_flow_mapping_key @expect_node mapping: true # Block sequence states expect_block_sequence: -> indentless = @mapping_context and not @indentation @increase_indent { indentless } @state = @expect_first_block_sequence_item expect_first_block_sequence_item: -> @expect_block_sequence_item true expect_block_sequence_item: (first = false) -> if not first and @event instanceof events.SequenceEndEvent @indent = @indents.pop() @state = @states.pop() else @write_indent() @write_indicator '-', true, indentation: true @states.push @expect_block_sequence_item @expect_node sequence: true # Block mapping states expect_block_mapping: -> @increase_indent() @state = @expect_first_block_mapping_key expect_first_block_mapping_key: -> @expect_block_mapping_key true expect_block_mapping_key: (first = false) -> if not first and @event instanceof events.MappingEndEvent @indent = @indents.pop() @state = @states.pop() else @write_indent() if @check_simple_key() @states.push @expect_block_mapping_simple_value @expect_node mapping: true, simple_key: true else @write_indicator '?', true, indentation: true @states.push @expect_block_mapping_value @expect_node mapping: true expect_block_mapping_simple_value: -> @write_indicator ':', false @states.push @expect_block_mapping_key @expect_node mapping: true expect_block_mapping_value: -> @write_indent() @write_indicator ':', true, indentation: true @states.push @expect_block_mapping_key @expect_node mapping: true # Checkers check_empty_document: -> return false if @event not instanceof events.DocumentStartEvent or @events.length is 0 event = @events[0] event instanceof events.ScalarEvent and not event.anchor? and not event.tag? and event.implicit and event.value is '' check_empty_sequence: -> @event instanceof events.SequenceStartEvent and @events[0] instanceof events.SequenceEndEvent check_empty_mapping: -> @event instanceof events.MappingStartEvent and @events[0] instanceof events.MappingEndEvent check_simple_key: -> length = 0 if @event instanceof events.NodeEvent and @event.anchor? @prepared_anchor ?= @prepare_anchor @event.anchor length += @prepared_anchor.length if @event.tag? and (@event instanceof events.ScalarEvent or @event instanceof events.CollectionStartEvent) @prepared_tag ?= @prepare_tag @event.tag length += @prepared_tag.length if @event instanceof events.ScalarEvent @analysis ?= @analyze_scalar @event.value length += @analysis.scalar.length length < 128 and ( @event instanceof events.AliasEvent or (@event instanceof events.ScalarEvent and not @analysis.empty and not @analysis.multiline) or @check_empty_sequence() or @check_empty_mapping() ) # Anchor, Tag and Scalar processors process_anchor: (indicator) -> unless @event.anchor? @prepared_anchor = null return @prepared_anchor ?= @prepare_anchor @event.anchor @write_indicator "#{indicator}#{@prepared_anchor}", true if @prepared_anchor @prepared_anchor = null process_tag: -> tag = @event.tag if @event instanceof events.ScalarEvent @style ?= @choose_scalar_style() if (not @canonical or not tag?) and ((@style is '' and @event.implicit[0]) or \ (@style isnt '' and @event.implicit[1])) @prepared_tag = null return if @event.implicit[0] and not tag? tag = '!' @prepared_tag = null else if (not @canonical or not tag?) and @event.implicit @prepared_tag = null return @error 'tag is not specified' if not tag? @prepared_tag ?= @prepare_tag tag @write_indicator @prepared_tag, true @prepared_tag = null process_scalar: -> @analysis ?= @analyze_scalar @event.value @style ?= @choose_scalar_style() split = not @simple_key_context switch @style when '"' then @write_double_quoted @analysis.scalar, split when "'" then @write_single_quoted @analysis.scalar, split when '>' then @write_folded @analysis.scalar when '|' then @write_literal @analysis.scalar else @write_plain @analysis.scalar, split @analysis = null @style = null choose_scalar_style: -> @analysis ?= @analyze_scalar @event.value return '"' if @event.style is '"' or @canonical return '' if not @event.style and @event.implicit[0] \ and not (@simple_key_context and (@analysis.empty or @analysis.multiline)) \ and ((@flow_level and @analysis.allow_flow_plain) \ or (not @flow_level and @analysis.allow_block_plain)) return @event.style if @event.style and @event.style in '|>' and not @flow_level \ and not @simple_key_context and @analysis.allow_block return "'" if (not @event.style or @event.style is "'") and @analysis.allow_single_quoted \ and not (@simple_key_context and @analysis.multiline) return '"' # Analyzers prepare_version: ([ major, minor ]) -> version = "#{major}.#{minor}" if major is 1 then version else @error 'unsupported YAML version', version prepare_tag_handle: (handle) -> unless handle @error 'tag handle must not be empty' if handle[0] isnt '!' or handle[-1..] isnt '!' @error "tag handle must start and end with '!':", handle for char in handle[1...-1] unless '0' <= char <= '9' or 'A' <= char <= 'Z' or 'a' <= char <= 'z' or char in '-_' @error "invalid character '#{char}' in the tag handle:", handle handle prepare_tag_prefix: (prefix) -> @error 'tag prefix must not be empty' unless prefix chunks = [] start = 0 end = +(prefix[0] is '!') while end < prefix.length char = prefix[end] if '0' <= char <= '9' or 'A' <= char <= 'Z' or 'a' <= char <= 'z' or char in '-;/?!:@&=+$,_.~*\'()[]' end++ else chunks.push prefix[start...end] if start < end start = end = end + 1 chunks.push char chunks.push prefix[start...end] if start < end chunks.join '' prepare_tag: (tag) -> @error 'tag must not be empty' unless tag return tag if tag is '!' handle = null suffix = tag for prefix in (k for own k of @tag_prefixes).sort() if tag.indexOf(prefix) is 0 and (prefix is '!' or prefix.length < tag.length) handle = @tag_prefixes[prefix] suffix = tag[prefix.length..] chunks = [] start = end = 0 while end < suffix.length char = suffix[end] if '0' <= char <= '9' or 'A' <= char <= 'Z' or 'a' <= char <= 'z' or \ char in '-;/?!:@&=+$,_.~*\'()[]' or (char is '!' and handle isnt '!') end++ else chunks.push suffix[start...end] if start < end start = end = end + 1 chunks.push char chunks.push suffix[start...end] if start < end suffix_text = chunks.join '' if handle then "#{handle}#{suffix_text}" else "!<#{suffix_text}>" prepare_anchor: (anchor) -> @error 'anchor must not be empty' unless anchor for char in anchor unless '0' <= char <= '9' or 'A' <= char <= 'Z' or 'a' <= char <= 'z' or char in '-_' @error "invalid character '#{char}' in the anchor:", anchor anchor analyze_scalar: (scalar) -> # Empty scalar is a special case. unless scalar new ScalarAnalysis(scalar, true, false, false, true, true, true, false) # Indicators and special characters. block_indicators = false flow_indicators = false line_breaks = false special_characters = false unicode_characters = false # Important whitespace combinations leading_space = false leading_break = false trailing_space = false trailing_break = false break_space = false space_break = false # Check document indicators. if scalar.indexOf('---') is 0 or scalar.indexOf('...') is 0 block_indicators = true flow_indicators = true # First character or preceded by a whitespace. preceded_by_whitespace = true # Last character or followed by a whitespace. followed_by_whitespace = scalar.length is 1 or scalar[1] in '\0 \t\r\n\x85\u2028\u2029' # The previous character is a space. previous_space = false # The previous character is a break previous_break = false index = 0 for char, index in scalar # Check for indicators. if index is 0 # Leading indicators are special characters. if char in '#,[]{}&*!|>\'"%@`' or (char is '-' and followed_by_whitespace) flow_indicators = true block_indicators = true else if char in '?:' flow_indicators = true block_indicators = true if followed_by_whitespace else # Some indicators cannot appear within a scalar as well. if char in ',?[]{}' flow_indicators = true else if char is ':' flow_indicators = true block_indicators = true if followed_by_whitespace else if char is '#' and preceded_by_whitespace flow_indicators = true block_indicators = true # Check for line breaks, special, and unicode characters. if char in '\n\x85\u2028\u2029' line_breaks = true unless char is '\n' or '\x20' <= char <= '\x7e' if char isnt '\uFEFF' and (char is '\x85' or '\xA0' <= char <= '\uD7FF' or '\uE000' <= char <= '\uFFFD') unicode_characters = true special_characters = true unless @allow_unicode else special_characters = true # Detect important whitespace combinations. if char is ' ' leading_space = true if index is 0 trailing_space = true if index == scalar.length - 1 break_space = true if previous_break previous_break = false previous_space = true else if char in '\n\x85\u2028\u2029' leading_break = true if index is 0 trailing_break = true if index == scalar.length - 1 space_break = true if previous_space previous_break = true previous_space = false else previous_break = false previous_space = false # Prepare for the next character. preceded_by_whitespace = char in C_WHITESPACE followed_by_whitespace = index + 2 >= scalar.length or scalar[index + 2] in C_WHITESPACE # Let's decide what styles are allowed. allow_flow_plain = true allow_block_plain = true allow_single_quoted = true allow_double_quoted = true allow_block = true # Leading and trailing whitespaces are bad for plain scalars. if leading_space or leading_break or trailing_space or trailing_break allow_flow_plain = allow_block_plain = false # We do not permit trailing spaces for block scalars. if trailing_space allow_block = false # Spaces at the beginning of a new line are only acceptable for block scalars. if break_space allow_flow_plain = allow_block_plain = allow_single_quoted = false # Spaces followed by breaks, as well as special character are only allowed for double quoted # scalars. if space_break or special_characters allow_flow_plain = allow_block_plain = allow_single_quoted = allow_block = false # Although the plain scalar writer supports breaks, we never emit multiline plain scalars. if line_breaks allow_flow_plain = allow_block_plain = false # Flow indicators are forbidden for flow plain scalars. if flow_indicators allow_flow_plain = false # Block indicators are forbidden for block plain scalars. if block_indicators allow_block_plain = false new ScalarAnalysis scalar, false, line_breaks, allow_flow_plain, allow_block_plain, allow_single_quoted, allow_double_quoted, allow_block # Writers ### Write BOM if needed. ### write_stream_start: -> if @encoding and @encoding.indexOf('utf-16') is 0 @stream.write '\uFEFF', @encoding write_stream_end: -> @flush_stream() write_indicator: (indicator, need_whitespace, options = {}) -> data = if @whitespace or not need_whitespace indicator else ' ' + indicator @whitespace = !!options.whitespace @indentation and= !!options.indentation @column += data.length @open_ended = false @stream.write data, @encoding write_indent: -> indent = @indent ? 0 if not @indentation or @column > indent or (@column == indent and not @whitespace) @write_line_break() if @column < indent @whitespace = true data = new Array(indent - @column + 1).join ' ' @column = indent @stream.write data, @encoding write_line_break: (data) -> @whitespace = true @indentation = true @line += 1 @column = 0 @stream.write data ? @best_line_break, @encoding write_version_directive: (version_text) -> @stream.write "%YAML #{version_text}", @encoding @write_line_break() write_tag_directive: (handle_text, prefix_text) -> @stream.write "%TAG #{handle_text} #{prefix_text}", @encoding @write_line_break() write_single_quoted: (text, split = true) -> @write_indicator "'", true spaces = false breaks = false start = end = 0 while end <= text.length char = text[end] if spaces if not char? or char isnt ' ' if start + 1 == end and @column > @best_width and split and start isnt 0 and end != text.length @write_indent() else data = text[start...end] @column += data.length @stream.write data, @encoding start = end else if breaks if not char? or char not in '\n\x85\u2028\u2029' @write_line_break() if text[start] is '\n' for br in text[start...end] if br is '\n' @write_line_break() else @write_line_break br @write_indent() start = end else if (not char? or char in ' \n\x85\u2028\u2029' or char is "'") and start < end data = text[start...end] @column += data.length @stream.write data, @encoding start = end if char is "'" @column += 2 @stream.write "''", @encoding start = end + 1 if char? spaces = char is ' ' breaks = char in '\n\x85\u2028\u2029' end++ @write_indicator "'", false write_double_quoted: (text, split = true) -> @write_indicator '"', true start = end = 0 while end <= text.length char = text[end] if not char? or char in '"\\\x85\u2028\u2029\uFEFF' or \ not ('\x20' <= char <= '\x7E' or (@allow_unicode and \ ('\xA0' <= char <= '\uD7FF' or '\uE000' <= char <= '\uFFFD'))) if start < end data = text[start...end] @column += data.length @stream.write data, @encoding start = end if char? data = if char of ESCAPE_REPLACEMENTS '\\' + ESCAPE_REPLACEMENTS[char] else if char <= '\xFF' "\\x#{util.pad_left util.to_hex(char), '0', 2}" else if char <= '\uFFFF' "\\u#{util.pad_left util.to_hex(char), '0', 4}" else "\\U#{util.pad_left util.to_hex(char), '0', 16}" @column += data.length @stream.write data, @encoding start = end + 1 if split and 0 < end < text.length - 1 and (char is ' ' or start >= end) and \ @column + (end - start) > @best_width data = "#{text[start...end]}\\" start = end if start < end @column += data.length @stream.write data, @encoding @write_indent() @whitespace = false @indentation = false if text[start] is ' ' data = '\\' @column += data.length @stream.write data, @encoding end++ @write_indicator '"', false write_folded: (text) -> hints = @determine_block_hints text @write_indicator ">#{hints}", true @open_ended = true if hints[-1..] is '+' @write_line_break() leading_space = true breaks = true spaces = false start = end = 0 while end <= text.length char = text[end] if breaks if not char? or char not in '\n\x85\u2028\u2029' if not leading_space and char? and char isnt ' ' and text[start] is '\n' @write_line_break() leading_space = char is ' ' for br in text[start...end] if br is '\n' @write_line_break() else @write_line_break br @write_indent() if char? start = end else if spaces if char isnt ' ' if start + 1 == end and @column > @best_width @write_indent() else data = text[start...end] @column += data.length @stream.write data, @encoding start = end else if not char? or char in ' \n\x85\u2028\u2029' data = text[start...end] @column += data.length @stream.write data, @encoding @write_line_break() if not char? start = end if char? breaks = char in '\n\x85\u2028\u2029' spaces = char is ' ' end++ write_literal: (text) -> hints = @determine_block_hints text @write_indicator "|#{hints}", true @open_ended = true if hints[-1..] is '+' @write_line_break() breaks = true start = end = 0 while end <= text.length char = text[end] if breaks if not char? or char not in '\n\x85\u2028\u2029' for br in text[start...end] if br is '\n' @write_line_break() else @write_line_break br @write_indent() if char? start = end else if not char? or char in '\n\x85\u2028\u2029' data = text[start...end] @stream.write data, @encoding @write_line_break() if not char? start = end breaks = char in '\n\x85\u2028\u2029' if char? end++ write_plain: (text, split = true) -> unless text then return @open_ended = true if @root_context unless @whitespace data = ' ' @column += data.length @stream.write data, @encoding @whitespace = false @indentation = false spaces = false breaks = false start = end = 0 while end <= text.length char = text[end] if spaces if char isnt ' ' if start + 1 == end and @column > @best_width and split @write_indent() @whitespace = false @indentation = false else data = text[start...end] @column += data.length @stream.write data, @encoding start = end else if breaks if char not in '\n\x85\u2028\u2029' @write_line_break() if text[start] is '\n' for br in text[start...end] if br is '\n' @write_line_break() else @write_line_break br @write_indent() @whitespace = false @indentation = false start = end else if not char? or char in ' \n\x85\u2028\u2029' data = text[start...end] @column += data.length @stream.write data, @encoding start = end if char? spaces = char is ' ' breaks = char in '\n\x85\u2028\u2029' end++ determine_block_hints: (text) -> hints = '' [ first, ..., penultimate, last ] = text if first in ' \n\x85\u2028\u2029' hints += @best_indent if last not in '\n\x85\u2028\u2029' hints += '-' else if text.length is 1 or penultimate in '\n\x85\u2028\u2029' hints += '+' hints flush_stream: -> @stream.flush?() ### Helper for common error pattern. ### error: (message, context) -> context = context?.constructor?.name ? util.inspect context if context throw new exports.EmitterError "#{message}#{if context then " #{context}" else ''}" class ScalarAnalysis constructor: (@scalar, @empty, @multiline, @allow_flow_plain, @allow_block_plain, @allow_single_quoted, @allow_double_quoted, @allow_block) ->