mirror of
				https://github.com/JuanCanham/HackMyResume.git
				synced 2025-10-31 05:07:26 +00:00 
			
		
		
		
	Merge implicit and explicit generation paths, start emitting file transform & copy signals, fix various bugs, introduce new bugs, support better --debug outputs in the future.
		
			
				
	
	
		
			293 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			CoffeeScript
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			CoffeeScript
		
	
	
	
	
	
| ###*
 | |
| 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
 |