dbay
Version:
In-Process, In-Memory & File-Based Relational Data Processing with SQLite, BetterSQLite3
330 lines (285 loc) • 14.2 kB
text/coffeescript
'use strict'
############################################################################################################
GUY = require 'guy'
{ alert
debug
help
info
plain
praise
urge
warn
whisper } = GUY.trm.get_loggers 'DBAY/STDLIB'
{ rpr
inspect
echo
log } = GUY.trm
E = require './errors'
#-----------------------------------------------------------------------------------------------------------
GUY = require 'guy'
### https://day.js.org ###
dayjs = require 'dayjs'
do =>
utc = require 'dayjs/plugin/utc'; dayjs.extend utc
relativeTime = require 'dayjs/plugin/relativeTime'; dayjs.extend relativeTime
toObject = require 'dayjs/plugin/toObject'; dayjs.extend toObject
customParseFormat = require 'dayjs/plugin/customParseFormat'; dayjs.extend customParseFormat
duration = require 'dayjs/plugin/duration'; dayjs.extend duration
#-----------------------------------------------------------------------------------------------------------
walk_split_parts = ( text, splitter, omit_empty ) ->
parts = text.split splitter
parts = ( part for part in parts when ( not omit_empty ) or ( part isnt '' ) )
count = parts.length
for part, idx in parts
lnr = idx + 1
rnr = count - idx
yield { lnr, rnr, part, }
return null
#-----------------------------------------------------------------------------------------------------------
@DBay_stdlib = ( clasz = Object ) => class extends clasz
### TAINT use `cfg` ###
#---------------------------------------------------------------------------------------------------------
_$stdlib_initialize: ->
GUY.props.hide @_me, '_dayjs', dayjs
GUY.props.hide @_me, '_dt_dbay_timestamp_input_template', 'YYYYMMDD-HHmmssZ'
GUY.props.hide @_me, '_dt_dbay_timestamp_output_template', 'YYYYMMDD-HHmmss[Z]'
@_me._stdlib_created = false
return null
#---------------------------------------------------------------------------------------------------------
create_stdlib: ->
return null if @_stdlib_created
@_stdlib_created = true
prefix = 'std_'
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'sql_i'
deterministic: true
varargs: false
call: ( name ) => @sql.I name
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'str_reverse'
deterministic: true
varargs: false
call: ( s ) -> ( Array.from s ).reverse().join ''
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'str_join'
deterministic: true
varargs: true
call: ( joiner, P... ) -> P.join joiner
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'str_is_blank'
deterministic: true
varargs: false
call: ( s ) -> if ( /^\s+$/ ).test s then 1 else 0
#-------------------------------------------------------------------------------------------------------
@create_table_function
name: prefix + 'str_split'
columns: [ 'lnr', 'rnr', 'part', ]
parameters: [ 'text', 'splitter', 'omit_empty', ]
deterministic: true
varargs: false
rows: ( text, splitter, omit_empty = false ) ->
omit_empty = !!omit_empty
yield from walk_split_parts text, splitter, omit_empty
return null
#-------------------------------------------------------------------------------------------------------
@create_table_function
name: prefix + 'str_split_re'
columns: [ 'lnr', 'rnr', 'part', ]
parameters: [ 'text', 'splitter', 'flags', 'omit_empty', ]
deterministic: false
varargs: true
rows: ( text, splitter, flags = null, omit_empty = false ) ->
omit_empty = !!omit_empty
if flags? then re = new RegExp splitter, flags
else re = new RegExp splitter
yield from walk_split_parts text, re, omit_empty
return null
#-------------------------------------------------------------------------------------------------------
@create_table_function
name: prefix + 'str_split_first'
columns: [ 'prefix', 'suffix', ]
parameters: [ 'text', 'splitter', ]
deterministic: true
varargs: false
rows: ( text, splitter ) ->
return null if ( text is null ) or ( splitter is null )
if ( idx = text.indexOf splitter ) < 0 then yield [ text, null, ]
else yield [ text[ ... idx ], text[ idx + 1 .. ], ]
return null
#-------------------------------------------------------------------------------------------------------
@create_table_function
name: prefix + 'generate_series'
columns: [ 'value', ]
parameters: [ 'start', 'stop', 'step', ]
varargs: true
deterministic: true
rows: ( start, stop = Infinity, step = 1 ) ->
### NOTE: `stop` differs from SQLite3, which has 9223372036854775807 ###
value = start
loop
break if value > stop
yield [ value, ]
value += step
return null
#-------------------------------------------------------------------------------------------------------
@create_table_function
name: prefix + 're_matches'
columns: [ 'match', 'capture', ]
parameters: [ 'text', 'pattern', ]
rows: ( text, pattern ) ->
regex = new RegExp pattern, 'g'
while ( match = regex.exec text )?
yield [ match[ 0 ], ( match[ 1 ] ? null ), ]
return null
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 're_is_match'
deterministic: false
varargs: false
call: ( text, pattern ) -> if ( new RegExp pattern, 'g' ).test text then 1 else 0
#=======================================================================================================
# ASSERTS AND EXCEPTIONS
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'echo'
deterministic: false
varargs: false
call: ( message ) -> echo message; return message
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'debug'
deterministic: false
varargs: false
call: ( message ) -> debug message; return message
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'info'
deterministic: false
varargs: false
call: ( message ) -> info message; return message
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'warn'
deterministic: false
varargs: false
call: ( message ) -> warn message; return message
#=======================================================================================================
# ASSERTS AND EXCEPTIONS
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'raise'
deterministic: true
varargs: false
call: ( message ) ->
throw new Error message
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'raise_json'
deterministic: true
varargs: false
call: ( facets_json ) ->
try facets = JSON.parse facets_json catch error
throw new Error "not a valid argument for std_raise_json: #{rpr facets}"
error = new Error facets.message ? "(no error message given)"
for k, v of facets
continue if k is 'message'
error[ k ] = v
throw error
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'assert'
deterministic: true
varargs: false
call: ( test, message ) ->
if ( not test? ) or ( test is 0 )
throw new Error message
return test
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'warn_if'
deterministic: true
varargs: false
call: ( test, message ) -> warn message if ( test is 1 ); test
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'warn_unless'
deterministic: true
varargs: false
call: ( test, message ) -> warn message if ( not test? ) or ( test is 0 ); test
#=======================================================================================================
# VARIABLES
#-------------------------------------------------------------------------------------------------------
@variables = {}
#-------------------------------------------------------------------------------------------------------
@create_function
name: prefix + 'getv'
deterministic: false
call: @getv.bind @
#-------------------------------------------------------------------------------------------------------
@create_table_function
name: prefix + 'variables',
deterministic: false,
columns: [ 'name', 'value', ]
parameters: []
rows: ( ( name ) -> yield [ name, ( @getv name ), ] for name of @variables ).bind @
#=======================================================================================================
# DATETIME
#-------------------------------------------------------------------------------------------------------
@create_function
### Returns a DBay_timestamp representing the present point in time. ###
name: prefix + 'dt_now'
deterministic: false
varargs: false
call: => @dt_now()
#-------------------------------------------------------------------------------------------------------
@create_function
### Given a DBay_timestamp, returns an English human-readable text indicating the remoteness of that
time relative to now, like 'four minutes ago' or 'in a week'. ###
name: prefix + 'dt_from_now'
deterministic: false
varargs: false
call: ( dbayts ) => @dt_from_now dbayts
#-------------------------------------------------------------------------------------------------------
return null
#=========================================================================================================
# VARIABLES (2)
#---------------------------------------------------------------------------------------------------------
setv: ( name, value ) -> @variables[ name ] = value
#---------------------------------------------------------------------------------------------------------
getv: ( name ) ->
if ( R = @variables[ name ] ) is undefined
throw new E.DBay_unknown_variable '^dbay/stdlib@1^', name
return switch
when R is true then 1
when R is false then 0
else R
#=========================================================================================================
# DATETIME (2)
#---------------------------------------------------------------------------------------------------------
dt_from_now: ( dbayts ) ->
return ( @dt_parse dbayts ).fromNow()
#---------------------------------------------------------------------------------------------------------
dt_now: ( cfg ) ->
@types.validate.dba_dt_now_cfg ( cfg = { @constructor.C.defaults.dba_dt_now_cfg..., cfg..., } )
R = @_dayjs().utc()
R = R.subtract cfg.subtract... if cfg.subtract?
R = R.add cfg.add... if cfg.add?
return R.format @_dt_dbay_timestamp_output_template
#---------------------------------------------------------------------------------------------------------
dt_dbayts_from_isots: ( isots ) -> ( @_dayjs isots ).utc().format @_dt_dbay_timestamp_output_template
#---------------------------------------------------------------------------------------------------------
dt_parse: ( dbayts ) ->
@types.validate.dbay_dt_timestamp dbayts
R = ( @_dayjs dbayts, @_dt_dbay_timestamp_input_template ).utc()
throw new E.DBay_invalid_timestamp '^dbay/stdlib@1^', dbayts unless @types.isa.dbay_dt_valid_dayjs R
return R
#---------------------------------------------------------------------------------------------------------
dt_format: ( dbayts, P... ) ->
R = @dt_parse dbayts
return R.format P...
#---------------------------------------------------------------------------------------------------------
dt_isots_from_dbayts: ( dbayts ) -> ( @dt_parse dbayts ).format()