l0bsterssg

node.js static responsive blog post generator
Log | Files | Refs | README

sprintf.js (8112B)


      1 (function(window) {
      2     var re = {
      3         not_string: /[^s]/,
      4         number: /[diefg]/,
      5         json: /[j]/,
      6         not_json: /[^j]/,
      7         text: /^[^\x25]+/,
      8         modulo: /^\x25{2}/,
      9         placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,
     10         key: /^([a-z_][a-z_\d]*)/i,
     11         key_access: /^\.([a-z_][a-z_\d]*)/i,
     12         index_access: /^\[(\d+)\]/,
     13         sign: /^[\+\-]/
     14     }
     15 
     16     function sprintf() {
     17         var key = arguments[0], cache = sprintf.cache
     18         if (!(cache[key] && cache.hasOwnProperty(key))) {
     19             cache[key] = sprintf.parse(key)
     20         }
     21         return sprintf.format.call(null, cache[key], arguments)
     22     }
     23 
     24     sprintf.format = function(parse_tree, argv) {
     25         var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ""
     26         for (i = 0; i < tree_length; i++) {
     27             node_type = get_type(parse_tree[i])
     28             if (node_type === "string") {
     29                 output[output.length] = parse_tree[i]
     30             }
     31             else if (node_type === "array") {
     32                 match = parse_tree[i] // convenience purposes only
     33                 if (match[2]) { // keyword argument
     34                     arg = argv[cursor]
     35                     for (k = 0; k < match[2].length; k++) {
     36                         if (!arg.hasOwnProperty(match[2][k])) {
     37                             throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]))
     38                         }
     39                         arg = arg[match[2][k]]
     40                     }
     41                 }
     42                 else if (match[1]) { // positional argument (explicit)
     43                     arg = argv[match[1]]
     44                 }
     45                 else { // positional argument (implicit)
     46                     arg = argv[cursor++]
     47                 }
     48 
     49                 if (get_type(arg) == "function") {
     50                     arg = arg()
     51                 }
     52 
     53                 if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) {
     54                     throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
     55                 }
     56 
     57                 if (re.number.test(match[8])) {
     58                     is_positive = arg >= 0
     59                 }
     60 
     61                 switch (match[8]) {
     62                     case "b":
     63                         arg = arg.toString(2)
     64                     break
     65                     case "c":
     66                         arg = String.fromCharCode(arg)
     67                     break
     68                     case "d":
     69                     case "i":
     70                         arg = parseInt(arg, 10)
     71                     break
     72                     case "j":
     73                         arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
     74                     break
     75                     case "e":
     76                         arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential()
     77                     break
     78                     case "f":
     79                         arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
     80                     break
     81                     case "g":
     82                         arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
     83                     break
     84                     case "o":
     85                         arg = arg.toString(8)
     86                     break
     87                     case "s":
     88                         arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)
     89                     break
     90                     case "u":
     91                         arg = arg >>> 0
     92                     break
     93                     case "x":
     94                         arg = arg.toString(16)
     95                     break
     96                     case "X":
     97                         arg = arg.toString(16).toUpperCase()
     98                     break
     99                 }
    100                 if (re.json.test(match[8])) {
    101                     output[output.length] = arg
    102                 }
    103                 else {
    104                     if (re.number.test(match[8]) && (!is_positive || match[3])) {
    105                         sign = is_positive ? "+" : "-"
    106                         arg = arg.toString().replace(re.sign, "")
    107                     }
    108                     else {
    109                         sign = ""
    110                     }
    111                     pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " "
    112                     pad_length = match[6] - (sign + arg).length
    113                     pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : "") : ""
    114                     output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg)
    115                 }
    116             }
    117         }
    118         return output.join("")
    119     }
    120 
    121     sprintf.cache = {}
    122 
    123     sprintf.parse = function(fmt) {
    124         var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
    125         while (_fmt) {
    126             if ((match = re.text.exec(_fmt)) !== null) {
    127                 parse_tree[parse_tree.length] = match[0]
    128             }
    129             else if ((match = re.modulo.exec(_fmt)) !== null) {
    130                 parse_tree[parse_tree.length] = "%"
    131             }
    132             else if ((match = re.placeholder.exec(_fmt)) !== null) {
    133                 if (match[2]) {
    134                     arg_names |= 1
    135                     var field_list = [], replacement_field = match[2], field_match = []
    136                     if ((field_match = re.key.exec(replacement_field)) !== null) {
    137                         field_list[field_list.length] = field_match[1]
    138                         while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
    139                             if ((field_match = re.key_access.exec(replacement_field)) !== null) {
    140                                 field_list[field_list.length] = field_match[1]
    141                             }
    142                             else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
    143                                 field_list[field_list.length] = field_match[1]
    144                             }
    145                             else {
    146                                 throw new SyntaxError("[sprintf] failed to parse named argument key")
    147                             }
    148                         }
    149                     }
    150                     else {
    151                         throw new SyntaxError("[sprintf] failed to parse named argument key")
    152                     }
    153                     match[2] = field_list
    154                 }
    155                 else {
    156                     arg_names |= 2
    157                 }
    158                 if (arg_names === 3) {
    159                     throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
    160                 }
    161                 parse_tree[parse_tree.length] = match
    162             }
    163             else {
    164                 throw new SyntaxError("[sprintf] unexpected placeholder")
    165             }
    166             _fmt = _fmt.substring(match[0].length)
    167         }
    168         return parse_tree
    169     }
    170 
    171     var vsprintf = function(fmt, argv, _argv) {
    172         _argv = (argv || []).slice(0)
    173         _argv.splice(0, 0, fmt)
    174         return sprintf.apply(null, _argv)
    175     }
    176 
    177     /**
    178      * helpers
    179      */
    180     function get_type(variable) {
    181         return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
    182     }
    183 
    184     function str_repeat(input, multiplier) {
    185         return Array(multiplier + 1).join(input)
    186     }
    187 
    188     /**
    189      * export to either browser or node.js
    190      */
    191     if (typeof exports !== "undefined") {
    192         exports.sprintf = sprintf
    193         exports.vsprintf = vsprintf
    194     }
    195     else {
    196         window.sprintf = sprintf
    197         window.vsprintf = vsprintf
    198 
    199         if (typeof define === "function" && define.amd) {
    200             define(function() {
    201                 return {
    202                     sprintf: sprintf,
    203                     vsprintf: vsprintf
    204                 }
    205             })
    206         }
    207     }
    208 })(typeof window === "undefined" ? this : window);