###* Definition of the FRESHTheme class. @module core/fresh-theme @license MIT. See LICENSE.md for details. ### FS = require 'fs' validator = require 'is-my-json-valid' _ = require 'underscore' PATH = require 'path' parsePath = require 'parse-filepath' pathExists = require('path-exists').sync EXTEND = require 'extend' HMSTATUS = require './status-codes' moment = require 'moment' loadSafeJson = require '../utils/safe-json-loader' READFILES = require 'recursive-readdir-sync' ### The FRESHTheme class is a representation of a FRESH theme asset. See also: JRSTheme. @class FRESHTheme ### class FRESHTheme ### Open and parse the specified theme. ### open: ( themeFolder ) -> this.folder = themeFolder; # Open the [theme-name].json file; should have the same name as folder pathInfo = parsePath( themeFolder ) # Set up a formats hash for the theme formatsHash = { } # Load the theme themeFile = PATH.join( themeFolder, 'theme.json' ) themeInfo = loadSafeJson( themeFile ) if themeInfo.ex throw fluenterror: if themeInfo.ex.operation == 'parse' then HMSTATUS.parseError else HMSTATUS.readError inner: themeInfo.ex.inner that = this # Move properties from the theme JSON file to the theme object EXTEND true, @, themeInfo.json # Check for an "inherits" entry in the theme JSON. if @inherits cached = { } _.each @inherits, (th, key) -> themesFolder = require.resolve 'fresh-themes' d = parsePath( themeFolder ).dirname themePath = PATH.join d, th cached[ th ] = cached[th] || new FRESHTheme().open( themePath ) formatsHash[ key ] = cached[ th ].getFormat( key ) # Load theme files formatsHash = _load.call @, formatsHash # Cache @formats = formatsHash # Set the official theme name @name = parsePath( @folder ).name @ ### Determine if the theme supports the specified output format. ### hasFormat: ( fmt ) -> _.has @formats, fmt ### Determine if the theme supports the specified output format. ### getFormat: ( fmt ) -> @formats[ fmt ] ### Load the theme implicitly, by scanning the theme folder for files. TODO: Refactor duplicated code with loadExplicit. ### _load = (formatsHash) -> # Set up a hash of formats supported by this theme. that = @ major = false # Establish the base theme folder tplFolder = PATH.join @folder, 'src' copyOnly = ['.ttf','.otf', '.png','.jpg','.jpeg','.pdf'] # Iterate over all files in the theme folder, producing an array, fmts, # containing info for each file. While we're doing that, also build up # the formatsHash object. fmts = READFILES(tplFolder).map (absPath) -> pathInfo = parsePath absPath absPathSafe = absPath.trim().toLowerCase() outFmt = '' isMajor = false # If this file is mentioned in the theme's JSON file under "transforms" if that.formats outFmt = _.find Object.keys( that.formats ), ( fmtKey ) -> fmtVal = that.formats[ fmtKey ] _.some fmtVal.transform, (fpath) -> absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase() absPathB == absPathSafe isMajor = true if outFmt if !outFmt # If this file lives in a specific format folder within the theme, # such as "/latex" or "/html", then that format is the output format # for all files within the folder. portion = pathInfo.dirname.replace tplFolder,'' if portion && portion.trim() return if portion[1] == '_' reg = /^(?:\/|\\)(html|latex|doc|pdf|png|partials)(?:\/|\\)?/ig res = reg.exec( portion ) if res if res[1] != 'partials' outFmt = res[1] else that.partials = that.partials || [] that.partials.push( { name: pathInfo.name, path: absPath } ) return null # Otherwise, the output format is inferred from the filename, as in # compact-[outputformat].[extension], for ex, compact-pdf.html. if !outFmt idx = pathInfo.name.lastIndexOf '-' outFmt = if idx == -1 then pathInfo.name else pathInfo.name.substr( idx + 1 ) isMajor = true act = if _.contains copyOnly, pathInfo.extname then 'copy' else 'transform' # We should have a valid output format now. formatsHash[ outFmt ] = formatsHash[outFmt] || { outFormat: outFmt, files: [] } if that.formats?[ outFmt ]?.symLinks formatsHash[ outFmt ].symLinks = that.formats[ outFmt ].symLinks # Create the file representation object. obj = action: act path: absPath major: isMajor orgPath: PATH.relative tplFolder, absPath ext: pathInfo.extname.slice 1 title: friendlyName outFmt pre: outFmt # outFormat: outFmt || pathInfo.name, data: FS.readFileSync absPath, 'utf8' css: null # Add this file to the list of files for this format type. formatsHash[ outFmt ].files.push( obj ) obj # Now, get all the CSS files... @cssFiles = fmts.filter (fmt) -> fmt and (fmt.ext == 'css') # For each CSS file, get its corresponding HTML file. It's possible that # a theme can have a CSS file but *no* HTML file, as when a theme author # creates a pure CSS override of an existing theme. @cssFiles.forEach (cssf) -> idx = _.findIndex fmts, ( fmt ) -> fmt && fmt.pre == cssf.pre && fmt.ext == 'html' cssf.major = false if idx > -1 fmts[ idx ].css = cssf.data fmts[ idx ].cssPath = cssf.path else if that.inherits # Found a CSS file without an HTML file in a theme that inherits # from another theme. This is the override CSS file. that.overrides = { file: cssf.path, data: cssf.data } formatsHash # ### # Load the theme explicitly, by following the 'formats' hash # in the theme's JSON settings file. # ### # loadExplicit = (formatsHash) -> # # # Housekeeping # tplFolder = PATH.join this.folder, 'src' # act = null # that = this # # # Iterate over all files in the theme folder, producing an array, fmts, # # containing info for each file. While we're doing that, also build up # # the formatsHash object. # fmts = READFILES( tplFolder ).map (absPath) -> # # act = null # # pathInfo = parsePath absPath # absPathSafe = absPath.trim().toLowerCase() # # # If this file is mentioned in the theme's JSON file under "transforms" # outFmt = _.find Object.keys( that.formats ), ( fmtKey ) -> # fmtVal = that.formats[ fmtKey ] # _.some fmtVal.transform, (fpath) -> # absPathB = PATH.join( that.folder, fpath ).trim().toLowerCase() # absPathB == absPathSafe # # act = 'transform' if outFmt # # # If this file lives in a specific format folder within the theme, # # such as "/latex" or "/html", then that format is the output format # # for all files within the folder. # if !outFmt # portion = pathInfo.dirname.replace tplFolder,'' # if portion && portion.trim() # reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig # res = reg.exec portion # res && (outFmt = res[1]) # # # Otherwise, the output format is inferred from the filename, as in # # compact-[outputformat].[extension], for ex, compact-pdf.html. # if !outFmt # idx = pathInfo.name.lastIndexOf '-' # outFmt = if (idx == -1) then pathInfo.name else pathInfo.name.substr(idx + 1) # # # We should have a valid output format now. # formatsHash[ outFmt ] = # formatsHash[ outFmt ] || { # outFormat: outFmt, # files: [], # symLinks: that.formats[ outFmt ].symLinks # }; # # # Create the file representation object. # obj = # action: act # orgPath: PATH.relative(that.folder, absPath) # path: absPath # ext: pathInfo.extname.slice(1) # title: friendlyName( outFmt ) # pre: outFmt # # outFormat: outFmt || pathInfo.name, # data: FS.readFileSync( absPath, 'utf8' ) # css: null # # # Add this file to the list of files for this format type. # formatsHash[ outFmt ].files.push( obj ) # obj # # # Now, get all the CSS files... # @cssFiles = fmts.filter ( fmt ) -> fmt.ext == 'css' # # # For each CSS file, get its corresponding HTML file # @cssFiles.forEach ( cssf ) -> # # For each CSS file, get its corresponding HTML file # idx = _.findIndex fmts, ( fmt ) -> # fmt.pre == cssf.pre && fmt.ext == 'html' # fmts[ idx ].css = cssf.data # fmts[ idx ].cssPath = cssf.path # # # Remove CSS files from the formats array # fmts = fmts.filter ( fmt) -> fmt.ext != 'css' # formatsHash ### Return a more friendly name for certain formats. TODO: Refactor ### friendlyName = ( val ) -> val = val.trim().toLowerCase() friendly = { yml: 'yaml', md: 'markdown', txt: 'text' } friendly[val] || val module.exports = FRESHTheme