Update functions.litcoffee

Update functions.litcoffee

### This file

The logic in this file builds the date object.

If you compare this file to the ```python/functions.py``` file
you'll notice they are very similiar. That's intentional,
this file was ported over and kept such that it would be somewhat
easy to port changes back and forth.

### Terminology

Because it's so confusing here is a list of the different levels to refer to:

- Groups
  -- This is the capture groups. The lowest level. ```([0-9])``` etc.
  "Day", "Month", etc.
- Expressions
  -- This is an expression with multiple capture groups. They're usually named
  after the person that wrote them. "Greg Burns", etc.

  In Python, Expressions functions have nice named arguments, def expr(Day, Month, Year),
  but Javascript doesn't support this.
- Type
  -- This is a collection of expressions with a common goal.
  Months (Jan, 0-12, January, etc.).
- Patterns
  -- This is a collection of types ```<Month>/<Day>/<Year>```

### Warning

A note about the above list. Types and Groups are commonly mixed up.
I think groups SHOULD be the lowest
(because they are the actual capture *groups* in regular expressions)
but a group of expressions also makes sense.

It's so confusing that there are at least a couple places here that I've
also mixed myself up and probably in the documentation for RE|PARSE as well.

---

I use named tuples in python, and ```_replace``` mirrors the functionality of
```date._replace```.. that is that it returns a copy of the date with only
certain things changed and leaves the old well enough alone.

        _replace = (_old, _new) ->
            year:   if _new.year? then _new.year else _old.year
            month:  if _new.month? then _new.month else _old.month
            day:    if _new.day? then _new.day  else _old.day
            hour:   if _new.hour? then _new.hour else _old.hour
            minute: if _new.minute? then _new.minute else _old.minute
            second: if _new.second? then _new.second else _old.second
            am_pm:  if _new.am_pm? then _new.am_pm else _old.am_pm

I represent lengths of dates (i.e. Thursday, 1-2pm) with a timedelta that goes right
after the date hash in the result array.

In Javascript, I'm just going to represent a timedelta with a hash.
I make a pretty dumb assumption that timedelta will only cross hour/minutes/second boundaries.

        timedelta_from_Date = (start_time, end_time) ->
            seconds_difference = 0
            if start_time.hour? and end_time.hour?
              seconds_difference += 60*60*(end_time.hour - start_time.hour)

              if start_time.minute? and end_time.minute?
                seconds_difference += 60*(end_time.minute - start_time.minute)

                if start_time.second? and end_time.second?
                  seconds_difference += end_time.second - start_time.second

            hours = Math.floor(seconds_difference/60/60)
            minutes = Math.floor(seconds_difference/60) - (hours * 60)
            seconds = seconds_difference - (minutes * 60) - (hours * 60 * 60)

            return {
               timedelta: true
               hours: hours
               minutes: minutes
               seconds: seconds
            }

This is just a helper function

        in_array = (arr, _) -> arr.indexOf(_) != -1

```progressive_match``` is useful for matching abbreviations
and full spellings. Functions like this could be useful
to help parse fuzzy matches in the future (i.e. misspellings of months).

        progressive_match = (string, possibilities) ->
            part = ""
            if not string? then return
            for character in string.toLowerCase()
                part += character
                matches = []
                for month, value of possibilities
                    if month.indexOf(part) > -1
                        matches.push(month)
                if matches.length == 1
                    return possibilities[matches[0]]
### Types

        month_to_number = (month_string) ->
          months =
              "january": 1
              "february": 2
              "march": 3
              "april": 4
              "may": 5
              "june": 6
              "july": 7
              "august": 8
              "september": 9
              "october": 10
              "november": 11
              "december": 12
           return progressive_match(month_string, months)

        weekday_to_number = (WeekdayString ) ->
            weekday_list = {
                "monday": 0,
                "tuesday": 1,
                "wednesday": 2,
                "thursday": 3,
                "friday": 4,
                "saturday": 5,
                "sunday": 6,
            }
            return progressive_match(WeekdayString, weekday_list)

I serialize am_pm by converting the hours to milliary/24-hour time format.
I also include ```am_pm: 'pm'``` in the date format to show that that transformation
was done. Making that clear helpful because if you get a time like 1, with no ```am_pm```
designation you might want to assume pm and add 12. This is not done in the libary because
date_machine strives for honesty, not guesswork.

        am_pm = (input) ->
            if not input?
              return
            if input.toLowerCase() == "am" then 0
            else if input.toLowerCase() == "pm" then 12

I try not to repeat logic and use other functions when possible.
I could put ```"Steve Valaitis2": time_expression``` to avoid this,
but I wrote it out to show that nothing special happens with 24-hour/military time.

        military_time = (MilHour, MilMinute, MilSecond ) ->
            return time_expression(MilHour, MilMinute, MilSecond, undefined)

Here is the am/pm conversion. I omit returns when the expressions are simple,
but later on I want to use them as much as possible to make my intentions clear.

        noon = (hour, AMPM) -> hour == 12 and AMPM == 12
        midnight = (hour, AMPM) -> hour == 12 and AMPM == 0

This function gets called pretty often, I'd consider it the bulk
of the output logic.

        time_expression = (Hour, Minute, Second, AMPM, SpecialTimeText ) ->
            if not Hour? and not Minute? and not Second? and not AMPM? and not SpecialTimeText?
                return
            if SpecialTimeText
                if SpecialTimeText.toLowerCase() == "noon"
                    return {hour: 12}
                if SpecialTimeText.toLowerCase() == "midnight"
                    return {hour: 0}
            d = {}
            if Hour
                d.hour = Number(Hour)
            if Minute
                d.minute = Number(Minute)
            if Second
                d.second = Number(Second)
            if AMPM?
                if d.hour
                    if noon(d.hour, AMPM)
                        d.hour = 12
                    else if midnight(d.hour, AMPM)
                        d.hour = 0
                    else
                        d.hour = d.hour + AMPM
                d.am_pm = AMPM
            if [d.hour, d.minute, d.second] == [undefined, undefined, undefined]
                return undefined
            return d

        time_and_time = (Hour, Minute, AMPM1, Hour2, Minute2, AMPM2) ->
            if AMPM2?
                AMPM2 = am_pm(AMPM2)
            if not AMPM1? and not AMPM2?
                return [time_expression(Hour, Minute), time_expression(Hour2, Minute2)]
            else if not AMPM1?
                return [time_expression(Hour, Minute, undefined, AMPM2),
                        time_expression(Hour2, Minute2, undefined, AMPM2)]
            else if not AMPM2?
                return [time_expression(Hour, Minute, undefined, AMPM1),
                        time_expression(Hour2, Minute2, undefined, AMPM1)]

        month_type = (input...) ->
            for value in input
                if value?
                    return Number(value)

        date_number = (Day, Abbr) -> if Day? then Number(Day)

        month_number = (MonthNum) -> Number(MonthNum)

        month_num_type = (input) -> Number(input)

        year = (Year) -> if Year? then Number(Year)

Sometimes the value is ```[undefined, undefined, <value>]```,
so I need to search for it and find it. There's enough
of these smaller expressions scattered around that moving them
all to one place might be good.

        get_first_defined = (_) ->
          for item in _
            if item?
              return item

### Patterns

There's a some unwrapping values out of arrays here.
Because there's no named-arguments, functions receive a lot more
arrays here then in Python and so they need to be unwrapped.

        weekday_range_with_time = (time1, time2, [weekday1], [weekday2], MonthRange) ->
            output = []

            # Get first undefined value
            # Since only one value in list will be defined (if any)
            time1 = get_first_defined(time1)
            time2 = get_first_defined(time2)

            if time1?
                for date in MonthRange
                    output.push(_replace(date, time1))
                    if time2?
                        output.push(_replace(date, time2))
            return output

        weekday_range_with_extra = ( time, and_time, weekday_start, weekday_end, extra_time, extra_weekday, MonthRange) ->
            output = []
            if time?
                for date in MonthRange
                    if date.weekday?
                        if date.weekday in [weekday_start .. weekday_end]
                            output.push(_replace(date, time))
                            if and_time?
                                output.push(_replace(date, and_time))
                        if date.weekday == extra_weekday
                            output.push(_replace(date, extra_time))
            return output

        weekday_range_with_extra_backwards = (extra_time, extra_weekday, time, weekday_start, weekday_end, MonthRange ) ->
            return weekday_range_with_extra(time[0], time[1], weekday_start, weekday_end, extra_time, extra_weekday, MonthRange )

        reverse_basic_text = (time, day, month, year) ->
            return basic_text(time, undefined, undefined, month, day, year)

        basic_text = (time, first_second, weekday, month, day, year) ->
            if not day?
                day = 1
            if (day instanceof Array) and day.length == 1
              day = day[0]
            if (year instanceof Array) and year.length == 1
              year = year[0]
            if not time?
                return {year: year, month: month, day: day}
            else
                year: year
                month: month
                day: day
                hour: time.hour
                minute: time.minute
                second: time.second
                am_pm: time.am_pm

        slash = (month, day, year, time) ->
            return basic_text(time, undefined, undefined, month, day, year)

        month_range = (month, date_a, date_b) ->
            output = []
            for day in [date_a .. date_b]
                output.push(basic_text(undefined, undefined, undefined, month, day, undefined))
            return output

        multi_time = (time1, BasicText) ->
            output = []
            if time1? and BasicText?
                output.push(_replace(BasicText, time1))
                output.push(BasicText)
                return output

        large_repeat_words = ->
            return undefined

        weekday_range_with_time_range = (time1, weekday_range) ->
            output = []
            if time1? and weekday_range?
                range = timedelta_from_Date(_replace(weekday_range[0], time1), weekday_range[0])
                for weekday in weekday_range
                    output.push(_replace(weekday, {hour:time1.hour, minute:time1.minute, am_pm:time1.am_pm}))
                    output.push(range)
                return output

        time_range = (unstrict_time, strict_time, date, reverse) ->
            endtime = date
            starttime = endtime
            if unstrict_time
                starttime = _replace(starttime, {hour:Number(unstrict_time[0])})
                if date.am_pm
                    starttime = _replace(starttime, {hour: starttime.hour + date.am_pm, am_pm:date.am_pm})
            else
                starttime = _replace(starttime, {hour: strict_time.hour, minute: strict_time.minute})

            time_range = timedelta_from_Date(starttime, endtime)
            if reverse
                time_range = timedelta_from_Date(endtime, starttime)
            return [starttime, time_range]

        alt_time_range = (first_second, weekday, month, day, year, time, unstrict_time, strict_time) ->
            return time_range(unstrict_time, strict_time, basic_text(time, first_second, weekday, month, day, year), reverse)

        until_range = (date1, date2) ->
            if date1 and date2
                return [date1, timedelta_from_Date(date1, date2)]

        through_range = (time, weekday1, weekday2, month, date, beginning) ->
            output = []
            if month and date and beginning and weekday1
                ending =
                    month: month
                    day: date
                for day in [beginning.day .. ending.day]
                    temp =
                        month: month
                        day: date
                    if temp.weekday and in_array([weekday1 .. weekday2], temp.weekday)
                        if time
                            output.push(_replace(temp, time))
                        else
                            output.push(temp)
                return output

### The function list

This hash/object matches all the functions with a
string that represents it in the parse tree.

        functions =
            # Groups
            "MonthString": month_to_number,
            "AMPM": am_pm,
            # Expressions
            "Chase Year": year,
            "Chase Date": date_number,
            "Chase Month": month_number,
            "Steve Valaitis": time_expression,
            "Greg Burns": (hour, am_pm) -> time_expression(hour, null, null, am_pm, null),
            "Greg Burns UnstrictTime:": time_expression,
            "Steve Valaitis2": military_time,
            "Steve Valaitis And Time": time_and_time,
            "Text Parts": time_expression,
            "Michael Ash10": weekday_to_number,
            # Types
            "Month": month_type,
            "MonthNum": month_num_type,
            # Patterns
            "LargeRepeatWords": large_repeat_words,
            "BasicText": basic_text,
            "ReverseBasicText": reverse_basic_text,
            "Slash": slash,
            "MonthRange": month_range,
            "MultiTime": multi_time,
            "WeekdayRangeWithTime": weekday_range_with_time,
            "WeekdayRangeWithExtra": weekday_range_with_extra,
            "WeekdayRangeWithExtraBackwards": weekday_range_with_extra_backwards,
            "WeekdayRangeWithTimeRange": weekday_range_with_time_range,
            "UntilRange": until_range,
            "ThroughRange": through_range,
            "TimeRange": time_range,
            "AltTimeRange": alt_time_range,

This is default expression or pattern. ```any``` means
if value is defined, and there is array with even a single defined value then return true.

            "_default": (_) ->
                if any(_) then _ else undefined

Recall ```Type``` here means collection of expressions.
There only one will matching expression due to the way regex works ```(a)|(b)```,
so you just have to find it and return it.

            "_default_type": (_...) ->
                if _?
                    for item in _
                         if item?
                             return item

            "_default_group": (_...) -> if _[0]? then _[0]

### This file

The logic in this file builds the date object.

If you compare this file to the ```python/functions.py``` file
you'll notice they are very similiar. That's intentional,
this file was ported over and kept such that it would be somewhat
easy to port changes back and forth.

### Terminology

Because it's so confusing here is a list of the different levels to refer to:

- Groups
  -- This is the capture groups. The lowest level. ```([0-9])``` etc.
  "Day", "Month", etc.
- Expressions
  -- This is an expression with multiple capture groups. They're usually named
  after the person that wrote them. "Greg Burns", etc.

  In Python, Expressions functions have nice named arguments, def expr(Day, Month, Year),
  but Javascript doesn't support this.
- Type
  -- This is a collection of expressions with a common goal.
  Months (Jan, 0-12, January, etc.).
- Patterns
  -- This is a collection of types ```<Month>/<Day>/<Year>```

### Warning

A note about the above list. Types and Groups are commonly mixed up.
I think groups SHOULD be the lowest
(because they are the actual capture *groups* in regular expressions)
but a group of expressions also makes sense.

It's so confusing that there are at least a couple places here that I've
also mixed myself up and probably in the documentation for RE|PARSE as well.

---

I use named tuples in python, and ```_replace``` mirrors the functionality of
```date._replace```.. that is that it returns a copy of the date with only
certain things changed and leaves the old well enough alone.

        _replace = (_old, _new) ->
            year:   if _new.year? then _new.year else _old.year
            month:  if _new.month? then _new.month else _old.month
            day:    if _new.day? then _new.day  else _old.day
            hour:   if _new.hour? then _new.hour else _old.hour
            minute: if _new.minute? then _new.minute else _old.minute
            second: if _new.second? then _new.second else _old.second
            am_pm:  if _new.am_pm? then _new.am_pm else _old.am_pm

I represent lengths of dates (i.e. Thursday, 1-2pm) with a timedelta that goes right
after the date hash in the result array.

In Javascript, I'm just going to represent a timedelta with a hash.
I make a pretty dumb assumption that timedelta will only cross hour/minutes/second boundaries.

        timedelta_from_Date = (start_time, end_time) ->
            seconds_difference = 0
            if start_time.hour? and end_time.hour?
              seconds_difference += 60*60*(end_time.hour - start_time.hour)

              if start_time.minute? and end_time.minute?
                seconds_difference += 60*(end_time.minute - start_time.minute)

                if start_time.second? and end_time.second?
                  seconds_difference += end_time.second - start_time.second

            hours = Math.floor(seconds_difference/60/60)
            minutes = Math.floor(seconds_difference/60) - (hours * 60)
            seconds = seconds_difference - (minutes * 60) - (hours * 60 * 60)

            return {
               timedelta: true
               hours: hours
               minutes: minutes
               seconds: seconds
            }

This is just a helper function

        in_array = (arr, _) -> arr.indexOf(_) != -1

```progressive_match``` is useful for matching abbreviations
and full spellings. Functions like this could be useful
to help parse fuzzy matches in the future (i.e. misspellings of months).

        progressive_match = (string, possibilities) ->
            part = ""
            if not string? then return
            for character in string.toLowerCase()
                part += character
                matches = []
                for month, value of possibilities
                    if month.indexOf(part) > -1
                        matches.push(month)
                if matches.length == 1
                    return possibilities[matches[0]]
### Types

        month_to_number = (month_string) ->
          months =
              "january": 1
              "february": 2
              "march": 3
              "april": 4
              "may": 5
              "june": 6
              "july": 7
              "august": 8
              "september": 9
              "october": 10
              "november": 11
              "december": 12
           return progressive_match(month_string, months)

        weekday_to_number = (WeekdayString ) ->
            weekday_list = {
                "monday": 0,
                "tuesday": 1,
                "wednesday": 2,
                "thursday": 3,
                "friday": 4,
                "saturday": 5,
                "sunday": 6,
            }
            return progressive_match(WeekdayString, weekday_list)

I serialize am_pm by converting the hours to milliary/24-hour time format.
I also include ```am_pm: 'pm'``` in the date format to show that that transformation
was done. Making that clear helpful because if you get a time like 1, with no ```am_pm```
designation you might want to assume pm and add 12. This is not done in the libary because
date_machine strives for honesty, not guesswork.

        am_pm = (input) ->
            if not input?
              return
            if input.toLowerCase() == "am" then 0
            else if input.toLowerCase() == "pm" then 12

I try not to repeat logic and use other functions when possible.
I could put ```"Steve Valaitis2": time_expression``` to avoid this,
but I wrote it out to show that nothing special happens with 24-hour/military time.

        military_time = (MilHour, MilMinute, MilSecond ) ->
            return time_expression(MilHour, MilMinute, MilSecond, undefined)

Here is the am/pm conversion. I omit returns when the expressions are simple,
but later on I want to use them as much as possible to make my intentions clear.

        noon = (hour, AMPM) -> hour == 12 and AMPM == 12
        midnight = (hour, AMPM) -> hour == 12 and AMPM == 0

This function gets called pretty often, I'd consider it the bulk
of the output logic.

        time_expression = (Hour, Minute, Second, AMPM, SpecialTimeText ) ->
            if not Hour? and not Minute? and not Second? and not AMPM? and not SpecialTimeText?
                return
            if SpecialTimeText
                if SpecialTimeText.toLowerCase() == "noon"
                    return {hour: 12}
                if SpecialTimeText.toLowerCase() == "midnight"
                    return {hour: 0}
            d = {}
            if Hour
                d.hour = Number(Hour)
            if Minute
                d.minute = Number(Minute)
            if Second
                d.second = Number(Second)
            if AMPM?
                if d.hour
                    if noon(d.hour, AMPM)
                        d.hour = 12
                    else if midnight(d.hour, AMPM)
                        d.hour = 0
                    else
                        d.hour = d.hour + AMPM
                d.am_pm = AMPM
            if [d.hour, d.minute, d.second] == [undefined, undefined, undefined]
                return undefined
            return d

        time_and_time = (Hour, Minute, AMPM1, Hour2, Minute2, AMPM2) ->
            if AMPM2?
                AMPM2 = am_pm(AMPM2)
            if not AMPM1? and not AMPM2?
                return [time_expression(Hour, Minute), time_expression(Hour2, Minute2)]
            else if not AMPM1?
                return [time_expression(Hour, Minute, undefined, AMPM2),
                        time_expression(Hour2, Minute2, undefined, AMPM2)]
            else if not AMPM2?
                return [time_expression(Hour, Minute, undefined, AMPM1),
                        time_expression(Hour2, Minute2, undefined, AMPM1)]

        month_type = (input...) ->
            for value in input
                if value?
                    return Number(value)

        date_number = (Day, Abbr) -> if Day? then Number(Day)

        month_number = (MonthNum) -> Number(MonthNum)

        month_num_type = (input) -> Number(input)

        year = (Year) -> if Year? then Number(Year)

Sometimes the value is ```[undefined, undefined, <value>]```,
so I need to search for it and find it. There's enough
of these smaller expressions scattered around that moving them
all to one place might be good.

        get_first_defined = (_) ->
          for item in _
            if item?
              return item

### Patterns

There's a some unwrapping values out of arrays here.
Because there's no named-arguments, functions receive a lot more
arrays here then in Python and so they need to be unwrapped.

        weekday_range_with_time = (time1, time2, [weekday1], [weekday2], MonthRange) ->
            output = []

            # Get first undefined value
            # Since only one value in list will be defined (if any)
            time1 = get_first_defined(time1)
            time2 = get_first_defined(time2)

            if time1?
                for date in MonthRange
                    output.push(_replace(date, time1))
                    if time2?
                        output.push(_replace(date, time2))
            return output

        weekday_range_with_extra = ( time, and_time, weekday_start, weekday_end, extra_time, extra_weekday, MonthRange) ->
            output = []
            if time?
                for date in MonthRange
                    if date.weekday?
                        if date.weekday in [weekday_start .. weekday_end]
                            output.push(_replace(date, time))
                            if and_time?
                                output.push(_replace(date, and_time))
                        if date.weekday == extra_weekday
                            output.push(_replace(date, extra_time))
            return output

        weekday_range_with_extra_backwards = (extra_time, extra_weekday, time, weekday_start, weekday_end, MonthRange ) ->
            return weekday_range_with_extra(time[0], time[1], weekday_start, weekday_end, extra_time, extra_weekday, MonthRange )

        reverse_basic_text = (time, day, month, year) ->
            return basic_text(time, undefined, undefined, month, day, year)

        basic_text = (time, first_second, weekday, month, day, year) ->
            if not day?
                day = 1
            if (day instanceof Array) and day.length == 1
              day = day[0]
            if (year instanceof Array) and year.length == 1
              year = year[0]
            if not time?
                return {year: year, month: month, day: day}
            else
                year: year
                month: month
                day: day
                hour: time.hour
                minute: time.minute
                second: time.second
                am_pm: time.am_pm

        slash = (month, day, year, time) ->
            return basic_text(time, undefined, undefined, month, day, year)

        month_range = (month, date_a, date_b) ->
            output = []
            for day in [date_a .. date_b]
                output.push(basic_text(undefined, undefined, undefined, month, day, undefined))
            return output

        multi_time = (time1, BasicText) ->
            output = []
            if time1? and BasicText?
                output.push(_replace(BasicText, time1))
                output.push(BasicText)
                return output

        large_repeat_words = ->
            return undefined

        weekday_range_with_time_range = (time1, weekday_range) ->
            output = []
            if time1? and weekday_range?
                range = timedelta_from_Date(_replace(weekday_range[0], time1), weekday_range[0])
                for weekday in weekday_range
                    output.push(_replace(weekday, {hour:time1.hour, minute:time1.minute, am_pm:time1.am_pm}))
                    output.push(range)
                return output

        time_range = (unstrict_time, strict_time, date, reverse) ->
            endtime = date
            starttime = endtime
            if unstrict_time
                starttime = _replace(starttime, {hour:Number(unstrict_time[0])})
                if date.am_pm
                    starttime = _replace(starttime, {hour: starttime.hour + date.am_pm, am_pm:date.am_pm})
            else
                starttime = _replace(starttime, {hour: strict_time.hour, minute: strict_time.minute})

            time_range = timedelta_from_Date(starttime, endtime)
            if reverse
                time_range = timedelta_from_Date(endtime, starttime)
            return [starttime, time_range]

        alt_time_range = (first_second, weekday, month, day, year, time, unstrict_time, strict_time) ->
            return time_range(unstrict_time, strict_time, basic_text(time, first_second, weekday, month, day, year), reverse)

        until_range = (date1, date2) ->
            if date1 and date2
                return [date1, timedelta_from_Date(date1, date2)]

        through_range = (time, weekday1, weekday2, month, date, beginning) ->
            output = []
            if month and date and beginning and weekday1
                ending =
                    month: month
                    day: date
                for day in [beginning.day .. ending.day]
                    temp =
                        month: month
                        day: date
                    if temp.weekday and in_array([weekday1 .. weekday2], temp.weekday)
                        if time
                            output.push(_replace(temp, time))
                        else
                            output.push(temp)
                return output

### The function list

This hash/object matches all the functions with a
string that represents it in the parse tree.

        functions =
            # Groups
            "MonthString": month_to_number,
            "AMPM": am_pm,
            # Expressions
            "Chase Year": year,
            "Chase Date": date_number,
            "Chase Month": month_number,
            "Steve Valaitis": time_expression,
            "Greg Burns": (hour, am_pm) -> time_expression(hour, null, null, am_pm, null),
            "Greg Burns UnstrictTime:": time_expression,
            "Steve Valaitis2": military_time,
            "Steve Valaitis And Time": time_and_time,
            "Text Parts": time_expression,
            "Michael Ash10": weekday_to_number,
            # Types
            "Month": month_type,
            "MonthNum": month_num_type,
            # Patterns
            "LargeRepeatWords": large_repeat_words,
            "BasicText": basic_text,
            "ReverseBasicText": reverse_basic_text,
            "Slash": slash,
            "MonthRange": month_range,
            "MultiTime": multi_time,
            "WeekdayRangeWithTime": weekday_range_with_time,
            "WeekdayRangeWithExtra": weekday_range_with_extra,
            "WeekdayRangeWithExtraBackwards": weekday_range_with_extra_backwards,
            "WeekdayRangeWithTimeRange": weekday_range_with_time_range,
            "UntilRange": until_range,
            "ThroughRange": through_range,
            "TimeRange": time_range,
            "AltTimeRange": alt_time_range,

This is default expression or pattern. ```any``` means
if value is defined, and there is array with even a single defined value then return true.

            "_default": (_) ->
                if any(_) then _ else undefined

Recall ```Type``` here means collection of expressions.
There only one will matching expression due to the way regex works ```"(a|b)```,
so you just have to find it and return it.

            "_default_type": (_...) ->
                if _?
                    for item in _
                         if item?
                             return item

            "_default_group": (_...) -> if _[0]? then _[0]


Flesh out json_platform logic a bit more

    JsonRenderer = require './json_renderer'

    class JsonPlatforms extends JsonRenderer

      init_gap: =>
        start:     undefined
        length:    undefined
        bottom_y:  undefined
        thickness: undefined

      open_gap: (j, ground, next, diff) =>
        init_thickness = Math.ceil Math.random() * diff

        bottom_y = (@config.wall.segments - ground.thickness) - @config.ground.max_height_diff
        bottom_y += init_thickness

        start:          j + 2
        length:         0
        bottom_y:       bottom_y
        max_thickness:  diff
        init_thickness: init_thickness
        thickness:      next.thickness

      do_render: (ctx, path) =>

        for block, i in ctx
          gap = @init_gap()

          opening_gap = false

          block.platforms = [ ]

          blk = ctx[i - 1]

          if blk?
            for k in blk.platforms
              if k.length > 0 && block.y == blk.y && blk.x == block.x - 1
                opening_gap = k.last() if k.last().x == @config.ground.segments - 2

          for ground, j in block.ground
            if blk? && opening_gap != false && j == 0
              next = blk.ground[@config.ground.segments - 2]

              gap =
                start:          @config.ground.segments - 2
                length:         2
                bottom_y:       opening_gap.y
                max_thickness:  @config.ground.max_height_diff
                init_thickness: opening_gap.thickness
                thickness:      next.thickness

              platform = @config.platformer gap

              blk.platforms.last().push.apply blk.platforms.last(), platform if platform?

              gap.length = 0
              gap.start  = 0
            else
              opening_gap = false

            if gap.length >= 0 && ground.thickness <= @config.wall.segments - gap.bottom_y - @config.ground.max_height_diff
              if j < block.ground.length - 1
                gap.length += 1
              else
                gap.length -= 1

                platform = @config.platformer gap, ground, j

                block.platforms.push platform if platform?

                gap = @init_gap()
            else if gap.length > 2
              gap.length -= 2

              platform = @config.platformer gap, ground, j

              block.platforms.push platform if platform?

              gap = @init_gap()
            # else if gap.length != undefined
            #   gap = @init_gap()
            else
              if j < block.ground.length - 1
                next = block.ground[j + 1]

                if ground.thickness? && next.thickness?
                  diff = ground.thickness - next.thickness
                  max_thickness = 1

                  if diff > 0
                    if diff < @config.ground.max_height_diff
                      max_thickness = Math.ceil Math.random() * diff
                    else
                      max_thickness = Math.ceil Math.random() * @config.ground.max_height_diff

                  gap = @open_gap j, ground, next, max_thickness

    module.exports = JsonPlatforms

    JsonRenderer = require './json_renderer'

    class JsonPlatforms extends JsonRenderer

      do_render: (ctx, path) =>
        ground_segments = @config.ground.segments

        for step, i in path
          ctx[i].ground = [ ]

          for g in [0...ground_segments]
            height = @config.grounder step, i, path, g, ctx[i]

            if height == -1
              ctx[i - 1].ground[g].height = null

              height = @config.grounder direction: 'FAKE', i, path, g

            ctx[i].ground.push height: height

    module.exports = JsonPlatforms


Fixed destroying non-html route

Fixed destroying non-html route

# Route

    'use strict'

    utils = require 'src/utils'
    signal = require 'src/signal'
    assert = require 'src/assert'
    log = require 'src/log'
    Schema = require 'src/schema'
    Networking = require 'src/networking'
    Document = require 'src/document'
    Renderer = require 'src/renderer'
    Dict = require 'src/dict'

    log = log.scope 'app', 'route'

    IS_NODE = process.env.NEFT_NODE
    IS_CLIENT = not IS_NODE

    module.exports = (app) -> class Route

        if IS_NODE
            usedTemplates = []
        else
            templates = Object.create null

        @lastClientRoute = null
        @onLastClientRouteChange = signal.create()

## *Document* Route.getTemplateComponent(*String* componentName)

        @getTemplateComponent = do ->
            if IS_NODE
                (name) ->
                    scope = app: app, routes: new Dict
                    tmpl = app.components[name].render null, scope
                    usedTemplates.push tmpl
                    tmpl
            else
                (name) ->
                    scope = app: app, routes: new Dict
                    templates[name] ?= app.components[name].render null, scope

## Route::constructor(*Object* options)

Access it with:
```javascript
module.exports = function(app) {
    var Route = app.Route;
};
```

Acceptable syntaxes:
```javascript
*Route* app.Route(*String* method, *String* uri, *Object* options)
*Route* app.Route(*String* methodWithUri, *Function* getData)
*Route* app.Route(*String* methodWithUri, *Object* options)
*Route* app.Route(*String* uri, *Function* getData)
*Route* app.Route(*String* uri, *Object* options)
*Route* app.Route(*String* method, *String* uri)
*Route* app.Route(*String* uri)
*Route* app.Route(*String* methodWithUri)
```

        constructor: (method, uri, opts) ->
            if utils.isObject(method)
                # opts
                opts = method
            else if utils.isObject(uri)
                opts = uri
            else unless utils.isObject(opts)
                opts = {}

            if typeof method is 'string' and typeof uri isnt 'string'
                opts.uri = method
            else if typeof method is 'string' and typeof uri is 'string'
                opts.method ?= method
                opts.uri ?= uri
            if typeof uri is 'function'
                opts.getData ?= uri

            assert.isObject opts
            opts = utils.clone opts

            if typeof opts.uri is 'string'
                # support methodWithUri e.g. 'get /home'
                spaceIndex = opts.uri.indexOf ' '
                if spaceIndex isnt -1
                    opts.method ?= opts.uri.slice 0, spaceIndex
                    opts.uri = opts.uri.slice spaceIndex + 1
                opts.uri = new Networking.Uri opts.uri
            assert.instanceOf opts.uri, Networking.Uri

            opts.method ?= 'get'
            assert.isString opts.method
            opts.method = opts.method.toLowerCase()
            assert.ok utils.has(Networking.Request.METHODS, opts.method)
            , "Networking doesn't provide a `#{opts.method}` method"

            if opts.schema?
                if utils.isPlainObject(opts.schema)
                    opts.schema = new Schema opts.schema
                assert.instanceOf opts.schema, Schema

            if opts.redirect?
                if typeof opts.redirect is 'string'
                    opts.redirect = new Networking.Uri opts.redirect
                else
                    assert.isFunction opts.redirect

            if utils.isObject(opts.toHTML)
                opts.toHTML = createToHTMLFromObject opts.toHTML

            for key, val of opts
                @[key] = val
            @__id__ = utils.uid()
            @app = app
            @name ||= getRouteName(@)

            app.networking.createHandler
                method: @method
                uri: @uri
                schema: @schema
                callback: utils.bindFunctionContext(handleRequest, @)

        getRouteName = (route) ->
            assert.instanceOf route, Route

            uri = route.uri._uri
            uri = uri.replace Networking.Uri.NAMES_RE, ''
            uri = uri.replace /\*/g, ''
            while uri.indexOf('//') isnt -1
                uri = uri.replace /\/\//g, '/'
            uri = uri.replace /^\//, ''
            uri = uri.replace /\/$/, ''
            uri

        routesCache = Object.create null
        pendingRoutes = Object.create null

        factoryRoute = do ->
            createInstance = (route) ->
                r = Object.create route
                r.__hash__ = utils.uid()
                r.factory?()
                r

            (route) ->
                assert.instanceOf route, Route

                id = route.__id__
                routesCache[id] ?= []
                r = routesCache[id].pop() or createInstance(route)
                r = Object.create r
                r.request = r.response = null
                r.route = r
                r._dataPrepared = false
                r._destroyComponentOnEnd = false
                r

        destroyRoute = (route) ->
            assert.instanceOf route, Route

            route.response.onSend.disconnect onResponseSent, route
            delete pendingRoutes[route.__hash__]
            route.destroy?()
            if route._dataPrepared
                switch route.request.type
                    when 'text'
                        route.destroyText?()
                    when 'json'
                        route.destroyJSON?()
                    when 'html'
                        route.destroyHTML?()

            if route._destroyComponentOnEnd
                route.response.data.destroy()

            routesCache[route.__id__].push Object.getPrototypeOf(route)

            if Route.lastClientRoute is route
                Route.lastClientRoute = null
                Route.onLastClientRouteChange.emit route

            return

        resolveSyncGetDataFunc = (route) ->
            assert.instanceOf route, Route

            route.data = route.getData()

        resolveAsyncGetDataFuncCallback = (route, err, data) ->
            assert.instanceOf route, Route

            if err?
                if route.response.status is 200
                    route.response.status = 500
                if route._dataPrepared and route.error is err
                    return false
                route.error = err
            else
                if route._dataPrepared and route.data is data
                    return false
                route.data = data
            true

        prepareRouteData = (route) ->
            assert.instanceOf route, Route
            {response} = route

            if route.error
                log.error "Error in route '#{route.uri}':\n#{route.error}"

            respData = response.data
            switch route.request.type
                when 'text'
                    data = route.toText()
                when 'json'
                    data = route.toJSON()
                when 'html'
                    data = route.toHTML()
                    if respData instanceof Document and route._destroyComponentOnEnd
                        respData.destroy()
                        response.data = null
                    if not (data instanceof Document) and response.data is respData
                        data = renderComponentFromConfig.call route, data
            route._dataPrepared = true
            if data?
                response.data = data
            else if response.data is respData
                response.data = ''

        onResponseSent = ->
            if IS_NODE or @request.type isnt 'html'
                destroyRoute @

                if IS_NODE and utils.has(usedTemplates, @response.data)
                    @response.data.destroy()
                    utils.remove usedTemplates, @response.data
            return

        finishRequest = (route) ->
            assert.instanceOf route, Route
            if IS_CLIENT and route.response.data instanceof Document
                app.windowItem.node = route.response.data.node
            if route.response.pending
                route.response.send()
            return

        callNextIfNeeded = (route, next) ->
            unless pendingRoutes[route.__hash__]
                if route.response.pending
                    next()
                return true
            false

        handleRequest = (req, res, next) ->
            assert.instanceOf req, Networking.Request
            assert.instanceOf res, Networking.Response
            assert.isFunction next

            route = factoryRoute @
            hash = route.__hash__
            assert.notOk pendingRoutes[hash]

            if IS_CLIENT and req.type is 'html'
                if Route.lastClientRoute
                    destroyRoute Route.lastClientRoute
                {lastClientRoute} = Route
                Route.lastClientRoute = route
                Route.onLastClientRouteChange.emit lastClientRoute

            route.request = req
            route.response = res
            pendingRoutes[hash] = true

            res.onSend onResponseSent, route

            # init
            route.init?()

            unless pendingRoutes[hash]
                return next()

            # redirect
            {redirect} = route
            if typeof redirect is 'function'
                redirect = route.redirect()
                unless pendingRoutes[hash]
                    return next()
            if typeof redirect is 'string'
                redirect = new Networking.Uri redirect
            if redirect instanceof Networking.Uri
                res.redirect redirect.toString(req.params)
                return

            # getData
            {getData} = route
            fakeAsync = false
            if typeof getData is 'function'
                if getData.length is 1
                    route.getData (err, data) ->
                        fakeAsync = true
                        if callNextIfNeeded(route, next)
                            return
                        unless resolveAsyncGetDataFuncCallback(route, err, data)
                            return
                        prepareRouteData route
                        if callNextIfNeeded(route, next)
                            return
                        finishRequest route
                else
                    resolveSyncGetDataFunc route
                    if callNextIfNeeded(route, next)
                        return
                    prepareRouteData route
                    if callNextIfNeeded(route, next)
                        return
                    finishRequest route
            else
                prepareRouteData route
                if callNextIfNeeded(route, next)
                    return
                finishRequest route

            if not fakeAsync and callNextIfNeeded(route, next)
                return

## *String* Route::method = `'get'`

## *Networking.Uri* Route::uri

## *App* Route::app

## *App.Route* Route::route

## *String* Route::name

## *Schema* Route::schema

## *Any* Route::data

## *Any* Route::error

## *Function* Route::factory()

## *Function* Route::init()

## *Function* Route::getData([*Function* callback])

## *Function* Route::destroy()

## *Function* Route::destroyJSON()

## *Function* Route::destroyText()

## *Function* Route::destroyHTML()

## *Networking.Uri* Route::redirect

Can be also a function. May returns a *Networking.Uri*, any String or `undefined`.

## *Networking.Request* Route::request

## *Networking.Response* Route::response

## *Function* Route::next()

        next: ->
            assert.ok pendingRoutes[@__hash__]
            destroyRoute @

## *Any* Route::toJSON()

        toJSON: ->
            if @response.status < 400
                @data?.toJSON?() or @data
            else
                @error.toJSON?() or @error

## *String* Route::toText()

        toText: ->
            if @response.status < 400
                @data + ''
            else
                @error + ''

## *Document* Route::toHTML()

        routeToString = ->
            "#{@method} #{@uri}"

        getDefaultRouteComponentName = ->
            path = "components/#{@name}.xhtml"
            if app.components[path]
                return path

        renderComponentFromConfig = (opts) ->
            componentName = opts?.component or
                getDefaultRouteComponentName.call(@) or
                'components/index.xhtml'
            tmplName = opts?.template or componentName
            useName = opts?.use or 'body'

            logTime = log.timer()
            if componentName isnt tmplName
                if tmpl = app.components[tmplName]
                    tmplComponent = Route.getTemplateComponent tmplName
                    tmplComponent.use useName, null
                else
                    log.warn """
                        Template component '#{tmplName}' can't be found \
                        for route '#{routeToString.call(@)}'
                    """
            if component = app.components[componentName]
                r = component.render null, @
            else
                log.warn "Component '#{tmplName}' can't be found for route '#{routeToString.call(@)}'"
            if tmplComponent
                if r?
                    r = tmplComponent.use(useName, r)
                else
                    r = tmplComponent
                if tmplComponent.context.routes.has(useName)
                    tmplComponent.context.routes.pop useName
                tmplComponent.context.routes.set useName, @
                @_destroyComponentOnEnd = false
            else
                @_destroyComponentOnEnd = true
            logTime.ok "Component `#{componentName}` rendered"
            r

        createToHTMLFromObject = (opts) ->
            -> renderComponentFromConfig.call @, opts

        toHTML: createToHTMLFromObject
            component: ''
            template: ''
            use: ''

# Route

    'use strict'

    utils = require 'src/utils'
    signal = require 'src/signal'
    assert = require 'src/assert'
    log = require 'src/log'
    Schema = require 'src/schema'
    Networking = require 'src/networking'
    Document = require 'src/document'
    Renderer = require 'src/renderer'
    Dict = require 'src/dict'

    log = log.scope 'app', 'route'

    IS_NODE = process.env.NEFT_NODE
    IS_CLIENT = not IS_NODE

    module.exports = (app) -> class Route

        if IS_NODE
            usedTemplates = []
        else
            templates = Object.create null

        @lastClientRoute = null
        @onLastClientRouteChange = signal.create()

## *Document* Route.getTemplateComponent(*String* componentName)

        @getTemplateComponent = do ->
            if IS_NODE
                (name) ->
                    scope = app: app, routes: new Dict
                    tmpl = app.components[name].render null, scope
                    usedTemplates.push tmpl
                    tmpl
            else
                (name) ->
                    scope = app: app, routes: new Dict
                    templates[name] ?= app.components[name].render null, scope

## Route::constructor(*Object* options)

Access it with:
```javascript
module.exports = function(app) {
    var Route = app.Route;
};
```

Acceptable syntaxes:
```javascript
*Route* app.Route(*String* method, *String* uri, *Object* options)
*Route* app.Route(*String* methodWithUri, *Function* getData)
*Route* app.Route(*String* methodWithUri, *Object* options)
*Route* app.Route(*String* uri, *Function* getData)
*Route* app.Route(*String* uri, *Object* options)
*Route* app.Route(*String* method, *String* uri)
*Route* app.Route(*String* uri)
*Route* app.Route(*String* methodWithUri)
```

        constructor: (method, uri, opts) ->
            if utils.isObject(method)
                # opts
                opts = method
            else if utils.isObject(uri)
                opts = uri
            else unless utils.isObject(opts)
                opts = {}

            if typeof method is 'string' and typeof uri isnt 'string'
                opts.uri = method
            else if typeof method is 'string' and typeof uri is 'string'
                opts.method ?= method
                opts.uri ?= uri
            if typeof uri is 'function'
                opts.getData ?= uri

            assert.isObject opts
            opts = utils.clone opts

            if typeof opts.uri is 'string'
                # support methodWithUri e.g. 'get /home'
                spaceIndex = opts.uri.indexOf ' '
                if spaceIndex isnt -1
                    opts.method ?= opts.uri.slice 0, spaceIndex
                    opts.uri = opts.uri.slice spaceIndex + 1
                opts.uri = new Networking.Uri opts.uri
            assert.instanceOf opts.uri, Networking.Uri

            opts.method ?= 'get'
            assert.isString opts.method
            opts.method = opts.method.toLowerCase()
            assert.ok utils.has(Networking.Request.METHODS, opts.method)
            , "Networking doesn't provide a `#{opts.method}` method"

            if opts.schema?
                if utils.isPlainObject(opts.schema)
                    opts.schema = new Schema opts.schema
                assert.instanceOf opts.schema, Schema

            if opts.redirect?
                if typeof opts.redirect is 'string'
                    opts.redirect = new Networking.Uri opts.redirect
                else
                    assert.isFunction opts.redirect

            if utils.isObject(opts.toHTML)
                opts.toHTML = createToHTMLFromObject opts.toHTML

            for key, val of opts
                @[key] = val
            @__id__ = utils.uid()
            @app = app
            @name ||= getRouteName(@)

            app.networking.createHandler
                method: @method
                uri: @uri
                schema: @schema
                callback: utils.bindFunctionContext(handleRequest, @)

        getRouteName = (route) ->
            assert.instanceOf route, Route

            uri = route.uri._uri
            uri = uri.replace Networking.Uri.NAMES_RE, ''
            uri = uri.replace /\*/g, ''
            while uri.indexOf('//') isnt -1
                uri = uri.replace /\/\//g, '/'
            uri = uri.replace /^\//, ''
            uri = uri.replace /\/$/, ''
            uri

        routesCache = Object.create null
        pendingRoutes = Object.create null

        factoryRoute = do ->
            createInstance = (route) ->
                r = Object.create route
                r.__hash__ = utils.uid()
                r.factory?()
                r

            (route) ->
                assert.instanceOf route, Route

                id = route.__id__
                routesCache[id] ?= []
                r = routesCache[id].pop() or createInstance(route)
                r = Object.create r
                r.request = r.response = null
                r.route = r
                r._dataPrepared = false
                r._destroyComponentOnEnd = false
                r

        destroyRoute = (route) ->
            assert.instanceOf route, Route

            route.response.onSend.disconnect onResponseSent, route
            delete pendingRoutes[route.__hash__]
            route.destroy?()
            if route._dataPrepared
                switch route.request.type
                    when 'text'
                        route.destroyText?()
                    when 'json'
                        route.destroyJSON?()
                    when 'html'
                        route.destroyHTML?()

            if route._destroyComponentOnEnd
                route.response.data.destroy()

            routesCache[route.__id__].push Object.getPrototypeOf(route)

            if Route.lastClientRoute is route
                Route.lastClientRoute = null
                Route.onLastClientRouteChange.emit route

            return

        resolveSyncGetDataFunc = (route) ->
            assert.instanceOf route, Route

            route.data = route.getData()

        resolveAsyncGetDataFuncCallback = (route, err, data) ->
            assert.instanceOf route, Route

            if err?
                if route.response.status is 200
                    route.response.status = 500
                if route._dataPrepared and route.error is err
                    return false
                route.error = err
            else
                if route._dataPrepared and route.data is data
                    return false
                route.data = data
            true

        prepareRouteData = (route) ->
            assert.instanceOf route, Route
            {response} = route

            if route.error
                log.error "Error in route '#{route.uri}':\n#{route.error}"

            respData = response.data
            switch route.request.type
                when 'text'
                    data = route.toText()
                when 'json'
                    data = route.toJSON()
                when 'html'
                    data = route.toHTML()
                    if respData instanceof Document and route._destroyComponentOnEnd
                        respData.destroy()
                        response.data = null
                    if not (data instanceof Document) and response.data is respData
                        data = renderComponentFromConfig.call route, data
            route._dataPrepared = true
            if data?
                response.data = data
            else if response.data is respData
                response.data = ''

        onResponseSent = ->
            if IS_NODE or @request.type isnt 'html'
                destroyRoute @

                if IS_NODE and utils.has(usedTemplates, @response.data)
                    @response.data.destroy()
                    utils.remove usedTemplates, @response.data
            return

        finishRequest = (route) ->
            assert.instanceOf route, Route
            if IS_CLIENT and route.response.data instanceof Document
                app.windowItem.node = route.response.data.node
            if route.response.pending
                route.response.send()
            return

        callNextIfNeeded = (route, next) ->
            unless pendingRoutes[route.__hash__]
                if route.response.pending
                    next()
                return true
            false

        handleRequest = (req, res, next) ->
            assert.instanceOf req, Networking.Request
            assert.instanceOf res, Networking.Response
            assert.isFunction next

            route = factoryRoute @
            hash = route.__hash__
            assert.notOk pendingRoutes[hash]

            if IS_CLIENT
                if Route.lastClientRoute
                    destroyRoute Route.lastClientRoute
                {lastClientRoute} = Route
                Route.lastClientRoute = route
                Route.onLastClientRouteChange.emit lastClientRoute

            route.request = req
            route.response = res
            pendingRoutes[hash] = true

            res.onSend onResponseSent, route

            # init
            route.init?()

            unless pendingRoutes[hash]
                return next()

            # redirect
            {redirect} = route
            if typeof redirect is 'function'
                redirect = route.redirect()
                unless pendingRoutes[hash]
                    return next()
            if typeof redirect is 'string'
                redirect = new Networking.Uri redirect
            if redirect instanceof Networking.Uri
                res.redirect redirect.toString(req.params)
                return

            # getData
            {getData} = route
            fakeAsync = false
            if typeof getData is 'function'
                if getData.length is 1
                    route.getData (err, data) ->
                        fakeAsync = true
                        if callNextIfNeeded(route, next)
                            return
                        unless resolveAsyncGetDataFuncCallback(route, err, data)
                            return
                        prepareRouteData route
                        if callNextIfNeeded(route, next)
                            return
                        finishRequest route
                else
                    resolveSyncGetDataFunc route
                    if callNextIfNeeded(route, next)
                        return
                    prepareRouteData route
                    if callNextIfNeeded(route, next)
                        return
                    finishRequest route
            else
                prepareRouteData route
                if callNextIfNeeded(route, next)
                    return
                finishRequest route

            if not fakeAsync and callNextIfNeeded(route, next)
                return

## *String* Route::method = `'get'`

## *Networking.Uri* Route::uri

## *App* Route::app

## *App.Route* Route::route

## *String* Route::name

## *Schema* Route::schema

## *Any* Route::data

## *Any* Route::error

## *Function* Route::factory()

## *Function* Route::init()

## *Function* Route::getData([*Function* callback])

## *Function* Route::destroy()

## *Function* Route::destroyJSON()

## *Function* Route::destroyText()

## *Function* Route::destroyHTML()

## *Networking.Uri* Route::redirect

Can be also a function. May returns a *Networking.Uri*, any String or `undefined`.

## *Networking.Request* Route::request

## *Networking.Response* Route::response

## *Function* Route::next()

        next: ->
            assert.ok pendingRoutes[@__hash__]
            destroyRoute @

## *Any* Route::toJSON()

        toJSON: ->
            if @response.status < 400
                @data?.toJSON?() or @data
            else
                @error.toJSON?() or @error

## *String* Route::toText()

        toText: ->
            if @response.status < 400
                @data + ''
            else
                @error + ''

## *Document* Route::toHTML()

        routeToString = ->
            "#{@method} #{@uri}"

        getDefaultRouteComponentName = ->
            path = "components/#{@name}.xhtml"
            if app.components[path]
                return path

        renderComponentFromConfig = (opts) ->
            componentName = opts?.component or
                getDefaultRouteComponentName.call(@) or
                'components/index.xhtml'
            tmplName = opts?.template or componentName
            useName = opts?.use or 'body'

            logTime = log.timer()
            if componentName isnt tmplName
                if tmpl = app.components[tmplName]
                    tmplComponent = Route.getTemplateComponent tmplName
                    tmplComponent.use useName, null
                else
                    log.warn """
                        Template component '#{tmplName}' can't be found \
                        for route '#{routeToString.call(@)}'
                    """
            if component = app.components[componentName]
                r = component.render null, @
            else
                log.warn "Component '#{tmplName}' can't be found for route '#{routeToString.call(@)}'"
            if tmplComponent
                if r?
                    r = tmplComponent.use(useName, r)
                else
                    r = tmplComponent
                if tmplComponent.context.routes.has(useName)
                    tmplComponent.context.routes.pop useName
                tmplComponent.context.routes.set useName, @
                @_destroyComponentOnEnd = false
            else
                @_destroyComponentOnEnd = true
            logTime.ok "Component `#{componentName}` rendered"
            r

        createToHTMLFromObject = (opts) ->
            -> renderComponentFromConfig.call @, opts

        toHTML: createToHTMLFromObject
            component: ''
            template: ''
            use: ''