sprintf.js
  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);