export type Keyword = string ; export type Selector = string ; export type Declarations = { [ property ] : number | string | Declarations ; } ; export type Rule = { type : "rule" ; selector : Selector ; declarations : Declarations ; } ; export type AtRule = { type : "at-rule" ; keyword : Keyword ; values ? : string [ ] ; declarations ? : Declarations ; rules ? : Rule [ ] ; } ; export type StynRule = Rule | AtRule ; export type StynTree = { rules : StynRule [ ] ; meta : { [ k ] : any ; [ k ] : any ; } ; } ; const i = ( content , count ) => `${ " " . repeat ( count ) }${ content }` ; const linewrap = ( content ) => content [ content . length - 1 ] === "\n" ? content : `${ content }\n` ; const block = ( content , _i = 0 ) => { const open = `{\n` ; const close = i ( "}" , _i ) ; return `${ open }${ linewrap ( content ) }${ close }\n` ; } ; const normalizeProp = ( prop ) => [ ... prop ] . map ( ( l ) => ( l . toLowerCase ( ) === l ? l : `-${ l . toLowerCase ( ) }` ) ) . join ( "" ) ; const join = ( arr , fn = ( x ) => x , ... args ) => { return arr . map ( ( item ) => fn ( item , ... args ) ) . map ( linewrap ) . join ( "" ) ; } ; export const stringify = ( tree ) => { const stringifyDeclarations = ( declarations , _i = 0 ) => { return block ( Object . keys ( declarations ) . map ( ( property ) => { const prop = normalizeProp ( property ) ; const value = declarations [ property ] ; if ( typeof value === "object" ) return undefined ; return linewrap ( i ( `${ prop }: ${ value };` , _i + 2 ) ) ; } ) . filter ( Boolean ) . join ( "" ) , _i ) ; } ; const stringifyRule = ( rule , _i = 0 ) => { const declarations = stringifyDeclarations ( rule . declarations , _i ) ; return linewrap ( i ( `${ rule . selector } ${ declarations }` , _i ) ) ; } ; const stringifyAtRule = ( atRule , _i = 0 ) => { const keyword = atRule . keyword ; const values = atRule . values ? ` ${ atRule . values . join ( " " ) }` : "" ; if ( atRule . declarations ) { const declarations = stringifyDeclarations ( atRule . declarations , _i ) ; return linewrap ( i ( `${ keyword }${ values } ${ declarations }` , _i ) ) ; } else if ( atRule . rules ) { const rules = atRule . rules . map ( ( rule ) => stringifyRule ( rule , _i + 2 ) ) . join ( "" ) ; return linewrap ( i ( `${ keyword }${ values } ${ block ( rules , _i ) }` , _i ) ) ; } else { return linewrap ( i ( `${ keyword }${ values };` , _i ) ) ; } } ; const stringifyStynRule = ( rule ) => { if ( rule . type === "at-rule" ) { return stringifyAtRule ( rule as AtRule ) ; } else if ( rule . type === "rule" ) { return stringifyRule ( rule as Rule ) ; } return "" ; } ; return join ( tree . rules . map ( stringifyStynRule ) . filter ( Boolean ) ) ; } ; export const parse = ( object ) : StynTree => { const rules = [ ] ; for ( const prop in object ) { const value = object [ prop ] ; if ( prop . startsWith ( "@" ) ) { const atRule = { type : "at-rule" , keyword : prop , } ; if ( prop . startsWith ( "@font-face" ) ) { atRule . declarations = value ; } else if ( typeof value === "object" ) { atRule . rules = [ ] ; for ( const selector in value ) { atRule . rules . push ( { type : "rule" , selector , declarations : value [ selector ] , } ) ; } } else if ( typeof value === "string" ) { atRule . values = [ value ] ; } rules . push ( atRule ) ; } else { const rule = { type : "rule" , selector : prop , declarations : value , } ; rules . push ( rule ) ; } } return { rules , meta : { } } ; } ; export type StynWalk = ( tree : StynTree , callback : ( r : StynRule , parent : StynRule [ ] , index : number ) => void ) => StynTree ; export const walk = ( tree , cb ) => { const treeCopy = { ... tree } ; for ( const rule of treeCopy . rules ) { cb ( rule , treeCopy . rules , treeCopy . rules . indexOf ( rule ) ) ; if ( rule . type === "at-rule" && typeof rule . rules !== "undefined" ) { for ( const childRule of rule . rules ) { cb ( childRule , rule . rules , rule . rules . indexOf ( childRule ) ) ; } } } return treeCopy ; } ;