ci-18n 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ (function(){var I18n;I18n=(function(){var innerLookup;function I18n(currentLocale,defaultLocale){if(currentLocale==null){currentLocale={};}
2
+ if(defaultLocale==null){defaultLocale=void 0;}
3
+ if(typeof currentLocale==='string'){this.localeString=currentLocale;}else{this.localeVal=currentLocale;}
4
+ if(typeof defaultLocale==='string'){this.defaultString=defaultLocale;}else if(!(defaultLocale!=null)){this.defaultString=I18n.getDefaultLanguage();}else{this.defaultVal=defaultLocale||{};}}
5
+ I18n.prototype.locale=function(){return this.localeVal||(this.localeVal=I18n.language(this.localeString));};I18n.prototype.defaultLocale=function(){return this.defaultVal||(this.defaultVal=I18n.language(this.defaultString));};innerLookup=function(locale,keywordList){var keyword,_i,_len;for(_i=0,_len=keywordList.length;_i<_len;_i++){keyword=keywordList[_i];if(locale==null){break;}
6
+ locale=locale[keyword];}
7
+ return locale;};I18n.prototype.translate=function(keywordList,options){var lookup;if(options==null){options={};}
8
+ keywordList=I18n.normalizeKeys(keywordList,options);lookup=innerLookup(this.locale(),keywordList)||options["default"]||innerLookup(this.defaultLocale(),keywordList);delete options.scope;delete options["default"];return I18n.interpolate(lookup,options);};I18n.prototype.localize=function(date,options){var match,matches,regexp,replacement_builder,string,_i,_len;if(!(date instanceof Date)){throw"Argument Error: "+date+" is not localizable";}
9
+ regexp=/%([a-z]|%)/ig;string=options.format;matches=string.match(regexp);if(matches==null){if(options.type==null){throw"Argument Error: missing type";}
10
+ if(options.type==="datetime"){options.type="time";}
11
+ string=this.translate(""+options.type+".formats."+options.format);if(string!=null){matches=string.match(regexp);}}
12
+ if(matches==null){throw"Argument Error: no such format";}
13
+ for(_i=0,_len=matches.length;_i<_len;_i++){match=matches[_i];match=match.slice(-1);replacement_builder=I18n.strftime[match];if(replacement_builder!=null){string=string.replace("%"+match,replacement_builder(date,this));}}
14
+ return string;};I18n.prototype.t=I18n.prototype.translate;I18n.prototype.l=I18n.prototype.localize;return I18n;})();I18n.normalizeKeys=function(keywords,options){var keyword,splitted_keywords,_i,_len,_ref;if(keywords==null){keywords=[];}
15
+ if(options==null){options={scope:[]};}
16
+ if(keywords instanceof Array){return keywords;}
17
+ splitted_keywords=[];_ref=keywords.split(".");for(_i=0,_len=_ref.length;_i<_len;_i++){keyword=_ref[_i];if((keyword!=null)&&keyword!==''){splitted_keywords.push(keyword);}}
18
+ return I18n.normalizeKeys(options.scope).concat(splitted_keywords);};(function(){var interpolate_basic,interpolate_sprintf;interpolate_basic=function(string,option,value){var new_string;new_string=string.replace(RegExp("%{"+option+"}","g"),value);if(string===new_string){return;}
19
+ return new_string;};interpolate_sprintf=function(string,option,value){var match,regexp,result;regexp=RegExp("%<"+option+">(.*?\\d*\\.?\\d*[bBdiouxXeEfgGcps])");match=string.match(regexp);if(match==null){return;}
20
+ result=sprintf("%(keyword)"+match[1],{keyword:value});return string.replace(match[0],result);};return I18n.interpolate=function(string,options){var new_string,option,value;if(options==null){options={};}
21
+ if(!(string!=null)){return string;}
22
+ for(option in options){value=options[option];new_string=interpolate_basic(string,option,value);new_string||(new_string=interpolate_sprintf(string,option,value));if(new_string==null){throw new Error("Missing placeholder for keyword \""+option+"\"");}
23
+ string=new_string;}
24
+ return string;};})();I18n.strftime={'d':function(date){return('0'+date.getDate()).slice(-2);},'b':function(date,i18n){return i18n.t("date.abbr_month_names")[date.getMonth()];},'B':function(date,i18n){return i18n.t("date.month_names")[date.getMonth()];},'a':function(date,i18n){return i18n.t("date.abbr_day_names")[date.getDay()];},'A':function(date,i18n){return i18n.t("date.day_names")[date.getDay()];},'Y':function(date){return date.getFullYear();},'m':function(date){return('0'+(date.getMonth()+1)).slice(-2);},'H':function(date){return('0'+(date.getHours())).slice(-2);},'M':function(date){return('0'+(date.getMinutes())).slice(-2);},'S':function(date){return('0'+(date.getSeconds())).slice(-2);},'z':function(date){var tz_offset;tz_offset=date.getTimezoneOffset();return(tz_offset>0&&'-'||'+')+('0'+(tz_offset/60)).slice(-2)+('0'+(tz_offset%60)).slice(-2);},'p':function(date,i18n){return i18n.t("time")[date.getHours()>=12&&'pm'||'am'];},'e':function(date){return date.getDate();},'I':function(date){return('0'+(date.getHours()%12)).slice(-2);},'j':function(date){return(((date.getTime()-new Date("Jan 1 "+date.getFullYear()).getTime())/(1000*60*60*24)+1)+'').split(/\./)[0];},'k':function(date){return date.getHours();},'l':function(date){return date.getHours()%12;},'w':function(date){return date.getDay();},'y':function(date){return(""+(date.getYear())).slice(-2);},'%':function(){return'%';}};(function(){var defaultLanguage,languages;languages={};defaultLanguage=void 0;I18n.addLanguage=function(name,lang){return languages[name]=lang;};I18n.clearLanguages=function(){return languages={};};I18n.language=function(name){return languages[name];};I18n.setDefaultLanguage=function(name){return defaultLanguage=name;};return I18n.getDefaultLanguage=function(){return defaultLanguage;};})();I18n.load=function(path,lang){var script,url;url=""+path+"/"+lang+".js";script=document.createElement('script');script.setAttribute('src',url);return document.getElementsByTagName('head')[0].appendChild(script);};I18n.detectLanguage=function(navigator){var name,_i,_len,_ref;_ref=["language","browserLanguage"];for(_i=0,_len=_ref.length;_i<_len;_i++){name=_ref[_i];if(navigator[name]!=null){return navigator[name];}}};I18n.autoloadAndSetup=function(options){var lang,langsToLoad,_i,_len;if(options.language==null){options.language=I18n.detectLanguage(window.navigator);}
25
+ langsToLoad=[options.language];if(options["default"]!=null){langsToLoad.push(options["default"]);}
26
+ for(_i=0,_len=langsToLoad.length;_i<_len;_i++){lang=langsToLoad[_i];I18n.load(options.path,lang);}
27
+ return I18n.setup(options.language,options["default"]);};I18n.setup=function(locale,defaultLocale){return window.$i18n=new I18n(locale,defaultLocale);};I18n.autosetup=function(defaultLocale){var locale;locale=I18n.detectLanguage(window.navigator);return I18n.setup(locale,defaultLocale);};window.I18n=I18n;}).call(this);
28
+ var sprintf=(function(){function get_type(variable){return Object.prototype.toString.call(variable).slice(8,-1).toLowerCase();}
29
+ function str_repeat(input,multiplier){for(var output=[];multiplier>0;output[--multiplier]=input){}
30
+ return output.join('');}
31
+ var str_format=function(){if(!str_format.cache.hasOwnProperty(arguments[0])){str_format.cache[arguments[0]]=str_format.parse(arguments[0]);}
32
+ return str_format.format.call(null,str_format.cache[arguments[0]],arguments);};str_format.format=function(parse_tree,argv){var cursor=1,tree_length=parse_tree.length,node_type='',arg,output=[],i,k,match,pad,pad_character,pad_length;for(i=0;i<tree_length;i++){node_type=get_type(parse_tree[i]);if(node_type==='string'){output.push(parse_tree[i]);}
33
+ else if(node_type==='array'){match=parse_tree[i];if(match[2]){arg=argv[cursor];for(k=0;k<match[2].length;k++){if(!arg.hasOwnProperty(match[2][k])){throw(sprintf('[sprintf] property "%s" does not exist',match[2][k]));}
34
+ arg=arg[match[2][k]];}}
35
+ else if(match[1]){arg=argv[match[1]];}
36
+ else{arg=argv[cursor++];}
37
+ if(/[^s]/.test(match[8])&&(get_type(arg)!='number')){throw(sprintf('[sprintf] expecting number but found %s',get_type(arg)));}
38
+ switch(match[8]){case'b':arg=arg.toString(2);break;case'c':arg=String.fromCharCode(arg);break;case'd':arg=parseInt(arg,10);break;case'e':arg=match[7]?arg.toExponential(match[7]):arg.toExponential();break;case'f':arg=match[7]?parseFloat(arg).toFixed(match[7]):parseFloat(arg);break;case'o':arg=arg.toString(8);break;case's':arg=((arg=String(arg))&&match[7]?arg.substring(0,match[7]):arg);break;case'u':arg=Math.abs(arg);break;case'x':arg=arg.toString(16);break;case'X':arg=arg.toString(16).toUpperCase();break;}
39
+ arg=(/[def]/.test(match[8])&&match[3]&&arg>=0?'+'+arg:arg);pad_character=match[4]?match[4]=='0'?'0':match[4].charAt(1):' ';pad_length=match[6]-String(arg).length;pad=match[6]?str_repeat(pad_character,pad_length):'';output.push(match[5]?arg+pad:pad+arg);}}
40
+ return output.join('');};str_format.cache={};str_format.parse=function(fmt){var _fmt=fmt,match=[],parse_tree=[],arg_names=0;while(_fmt){if((match=/^[^\x25]+/.exec(_fmt))!==null){parse_tree.push(match[0]);}
41
+ else if((match=/^\x25{2}/.exec(_fmt))!==null){parse_tree.push('%');}
42
+ else if((match=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt))!==null){if(match[2]){arg_names|=1;var field_list=[],replacement_field=match[2],field_match=[];if((field_match=/^([a-z_][a-z_\d]*)/i.exec(replacement_field))!==null){field_list.push(field_match[1]);while((replacement_field=replacement_field.substring(field_match[0].length))!==''){if((field_match=/^\.([a-z_][a-z_\d]*)/i.exec(replacement_field))!==null){field_list.push(field_match[1]);}
43
+ else if((field_match=/^\[(\d+)\]/.exec(replacement_field))!==null){field_list.push(field_match[1]);}
44
+ else{throw('[sprintf] huh?');}}}
45
+ else{throw('[sprintf] huh?');}
46
+ match[2]=field_list;}
47
+ else{arg_names|=2;}
48
+ if(arg_names===3){throw('[sprintf] mixing positional and named placeholders is not (yet) supported');}
49
+ parse_tree.push(match);}
50
+ else{throw('[sprintf] huh?');}
51
+ _fmt=_fmt.substring(match[0].length);}
52
+ return parse_tree;};return str_format;})();var vsprintf=function(fmt,argv){argv.unshift(fmt);return sprintf.apply(null,argv);};
@@ -0,0 +1,81 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ci-18n}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Matteo Collina"]
12
+ s.date = %q{2011-06-08}
13
+ s.description = %q{A localization library for javascript files in ruby on rails.}
14
+ s.email = %q{hello@matteocollina.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".rvmrc",
20
+ "Guardfile",
21
+ "README.md",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "build/.gitkeep",
25
+ "build/ci-18n.js",
26
+ "build/ci-18n.min.js",
27
+ "ci-18n.gemspec",
28
+ "coffeescripts/I18n.coffee",
29
+ "javascripts/sprintf-0.7-beta1.js",
30
+ "lib/ci-18n.rb",
31
+ "lib/ci18n.rb",
32
+ "spec/coffeescripts/autoloadSpec.coffee",
33
+ "spec/coffeescripts/helpers/SpecHelper.coffee",
34
+ "spec/coffeescripts/languageRepositorySpec.coffee",
35
+ "spec/coffeescripts/localizeSpec.coffee",
36
+ "spec/coffeescripts/translateSpec.coffee",
37
+ "spec/coffeescripts/utilsSpec.coffee",
38
+ "spec/javascripts/support/jasmine.yml",
39
+ "spec/javascripts/support/jasmine_config.rb",
40
+ "spec/javascripts/support/jasmine_runner.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/mcollina/ci-18n}
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.5.3}
46
+ s.summary = %q{A localization library for javascript files in ruby on rails.}
47
+
48
+ if s.respond_to? :specification_version then
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
54
+ s.add_development_dependency(%q<coffee-script>, ["~> 2.2.0"])
55
+ s.add_development_dependency(%q<jasmine>, ["~> 1.0"])
56
+ s.add_development_dependency(%q<guard>, ["~> 0.3.1"])
57
+ s.add_development_dependency(%q<guard-coffeescript>, ["~> 0.2.0"])
58
+ s.add_development_dependency(%q<guard-livereload>, ["~> 0.1.9"])
59
+ s.add_development_dependency(%q<rake-minify>, ["~> 0.3"])
60
+ else
61
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
62
+ s.add_dependency(%q<jeweler>, [">= 0"])
63
+ s.add_dependency(%q<coffee-script>, ["~> 2.2.0"])
64
+ s.add_dependency(%q<jasmine>, ["~> 1.0"])
65
+ s.add_dependency(%q<guard>, ["~> 0.3.1"])
66
+ s.add_dependency(%q<guard-coffeescript>, ["~> 0.2.0"])
67
+ s.add_dependency(%q<guard-livereload>, ["~> 0.1.9"])
68
+ s.add_dependency(%q<rake-minify>, ["~> 0.3"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
+ s.add_dependency(%q<jeweler>, [">= 0"])
73
+ s.add_dependency(%q<coffee-script>, ["~> 2.2.0"])
74
+ s.add_dependency(%q<jasmine>, ["~> 1.0"])
75
+ s.add_dependency(%q<guard>, ["~> 0.3.1"])
76
+ s.add_dependency(%q<guard-coffeescript>, ["~> 0.2.0"])
77
+ s.add_dependency(%q<guard-livereload>, ["~> 0.1.9"])
78
+ s.add_dependency(%q<rake-minify>, ["~> 0.3"])
79
+ end
80
+ end
81
+
@@ -0,0 +1,220 @@
1
+
2
+ class I18n
3
+
4
+ # the first parameter is the main locale object
5
+ # while the second is the fallback (default)
6
+ constructor: (currentLocale = {}, defaultLocale = undefined)->
7
+ if typeof currentLocale == 'string'
8
+ @localeString = currentLocale
9
+ else
10
+ @localeVal = currentLocale
11
+
12
+ if typeof defaultLocale == 'string'
13
+ @defaultString = defaultLocale
14
+ else if not defaultLocale?
15
+ @defaultString = I18n.getDefaultLanguage()
16
+ else
17
+ @defaultVal = defaultLocale || {}
18
+
19
+ locale: ->
20
+ @localeVal ||= I18n.language(@localeString)
21
+
22
+ defaultLocale: ->
23
+ @defaultVal ||= I18n.language(@defaultString)
24
+
25
+
26
+ # this is a private function
27
+ innerLookup = (locale, keywordList) ->
28
+ for keyword in keywordList
29
+ break unless locale?
30
+ locale = locale[keyword]
31
+ locale
32
+
33
+ translate: (keywordList, options = {}) ->
34
+ keywordList = I18n.normalizeKeys(keywordList, options)
35
+ lookup = innerLookup(this.locale(), keywordList) || options.default || innerLookup(this.defaultLocale(), keywordList)
36
+
37
+ # the scope is used by normalizeKeys, but it will be
38
+ # interpreted as a keyword placeholder by I18n.interpolate
39
+ delete options.scope
40
+
41
+ # the default is used here, but it will be interpreted
42
+ # as a keyword placeholder by I18n.interpolate
43
+ delete options.default
44
+
45
+ I18n.interpolate(lookup, options)
46
+
47
+ localize: (date, options) ->
48
+ throw "Argument Error: #{date} is not localizable" unless date instanceof Date
49
+ regexp = /%([a-z]|%)/ig
50
+ string = options.format
51
+ matches = string.match(regexp)
52
+ unless matches?
53
+ throw "Argument Error: missing type" unless options.type?
54
+ options.type = "time" if options.type == "datetime"
55
+ string = this.translate("#{options.type}.formats.#{options.format}")
56
+ matches = string.match(regexp) if string?
57
+
58
+ throw "Argument Error: no such format" unless matches?
59
+
60
+ for match in matches
61
+ match = match.slice(-1)
62
+ replacement_builder = I18n.strftime[match]
63
+ string = string.replace("%#{match}", replacement_builder(date, this)) if replacement_builder?
64
+ string
65
+
66
+ t: this::translate # coffeescript syntax to alias a method
67
+ l: this::localize # coffeescript syntax to alias a method
68
+
69
+ # extract an array of keys from the dot separated string
70
+ I18n.normalizeKeys = (keywords = [], options = { scope: [] }) ->
71
+ return keywords if keywords instanceof Array
72
+
73
+ splitted_keywords = []
74
+ for keyword in keywords.split(".")
75
+ # it will be much better if we could just use filter
76
+ splitted_keywords.push(keyword) if keyword? and keyword != ''
77
+ I18n.normalizeKeys(options.scope).concat(splitted_keywords)
78
+
79
+ # interpolate function wrapper
80
+ ( ->
81
+ interpolate_basic = (string, option, value) ->
82
+ new_string = string.replace(///%{#{option}}///g, value)
83
+ return undefined if string == new_string
84
+ new_string
85
+
86
+ interpolate_sprintf = (string, option, value) ->
87
+ # this regexp was taken from https://github.com/svenfuchs/i18n/blob/master/lib/i18n/interpolate/ruby.rb
88
+ regexp = ///%<#{option}>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/// # matches placeholders like "%<foo>.d"
89
+ match = string.match(regexp)
90
+ return undefined unless match?
91
+
92
+ result = sprintf("%(keyword)#{match[1]}", keyword: value)
93
+ string.replace(match[0], result)
94
+
95
+ I18n.interpolate = (string, options = {}) ->
96
+ return string if not string?
97
+ for option, value of options
98
+ new_string = interpolate_basic(string, option, value)
99
+ new_string ||= interpolate_sprintf(string, option, value)
100
+ unless new_string?
101
+ throw new Error("Missing placeholder for keyword \"#{option}\"")
102
+ string = new_string
103
+ string
104
+ )()
105
+
106
+ I18n.strftime = {
107
+ 'd': (date) ->
108
+ ('0' + date.getDate()).slice(-2)
109
+
110
+ 'b': (date, i18n) ->
111
+ i18n.t("date.abbr_month_names")[date.getMonth()]
112
+
113
+ 'B': (date, i18n) ->
114
+ i18n.t("date.month_names")[date.getMonth()]
115
+
116
+ 'a': (date, i18n) ->
117
+ i18n.t("date.abbr_day_names")[date.getDay()]
118
+
119
+ 'A': (date, i18n) ->
120
+ i18n.t("date.day_names")[date.getDay()]
121
+
122
+ 'Y': (date) ->
123
+ date.getFullYear()
124
+
125
+ 'm': (date) ->
126
+ ('0'+(date.getMonth() + 1)).slice(-2)
127
+
128
+ 'H': (date) ->
129
+ ('0'+(date.getHours())).slice(-2)
130
+
131
+ 'M': (date) ->
132
+ ('0'+(date.getMinutes())).slice(-2)
133
+
134
+ 'S': (date) ->
135
+ ('0'+(date.getSeconds())).slice(-2)
136
+
137
+ 'z': (date) ->
138
+ tz_offset = date.getTimezoneOffset()
139
+ (tz_offset > 0 and '-' or '+') + ('0' + (tz_offset / 60)).slice(-2) + ('0' + (tz_offset % 60)).slice(-2)
140
+
141
+ 'p': (date, i18n) ->
142
+ i18n.t("time")[date.getHours() >= 12 and 'pm' or 'am']
143
+
144
+ 'e': (date) ->
145
+ date.getDate()
146
+
147
+ 'I': (date) ->
148
+ ('0'+(date.getHours() % 12)).slice(-2)
149
+
150
+ 'j': (date) ->
151
+ (((date.getTime() - new Date("Jan 1 " + date.getFullYear()).getTime()) / (1000 * 60 * 60 * 24) + 1) + '').split(/\./)[0]
152
+
153
+ 'k': (date) ->
154
+ date.getHours()
155
+
156
+ 'l': (date) ->
157
+ date.getHours() % 12
158
+
159
+ 'w': (date) ->
160
+ date.getDay()
161
+
162
+ 'y': (date) ->
163
+ "#{date.getYear()}".slice(-2)
164
+
165
+ '%': -> '%'
166
+ }
167
+
168
+ (->
169
+ languages = {}
170
+ defaultLanguage = undefined
171
+
172
+ I18n.addLanguage = (name, lang) ->
173
+ languages[name] = lang
174
+
175
+ I18n.clearLanguages = ->
176
+ languages = {}
177
+
178
+ I18n.language = (name) ->
179
+ languages[name]
180
+
181
+ I18n.setDefaultLanguage = (name) ->
182
+ defaultLanguage = name
183
+
184
+ I18n.getDefaultLanguage = ->
185
+ defaultLanguage
186
+ )()
187
+
188
+ I18n.load = (path, lang) ->
189
+ url = "#{path}/#{lang}.js" # url of the external script
190
+
191
+ # dynamic script insertion
192
+ script = document.createElement('script')
193
+ script.setAttribute('src', url)
194
+
195
+ # load the script
196
+ document.getElementsByTagName('head')[0].appendChild(script);
197
+
198
+ I18n.detectLanguage = (navigator) ->
199
+ for name in ["language", "browserLanguage"]
200
+ return navigator[name] if navigator[name]?
201
+
202
+ I18n.autoloadAndSetup = (options) ->
203
+ options.language = I18n.detectLanguage(window.navigator) unless options.language?
204
+ langsToLoad = [options.language]
205
+ langsToLoad.push(options.default) if options.default?
206
+
207
+ for lang in langsToLoad
208
+ I18n.load(options.path, lang)
209
+
210
+ I18n.setup(options.language, options.default)
211
+
212
+ I18n.setup = (locale, defaultLocale) ->
213
+ window.$i18n = new I18n(locale, defaultLocale)
214
+
215
+ I18n.autosetup = (defaultLocale) ->
216
+ locale = I18n.detectLanguage(window.navigator)
217
+ I18n.setup(locale, defaultLocale)
218
+
219
+ # export I18n object to the world!
220
+ window.I18n = I18n
@@ -0,0 +1,183 @@
1
+ /**
2
+ sprintf() for JavaScript 0.7-beta1
3
+ http://www.diveintojavascript.com/projects/javascript-sprintf
4
+
5
+ Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions are met:
10
+ * Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+ * Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+ * Neither the name of sprintf() for JavaScript nor the
16
+ names of its contributors may be used to endorse or promote products
17
+ derived from this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
23
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+
31
+ Changelog:
32
+ 2010.09.06 - 0.7-beta1
33
+ - features: vsprintf, support for named placeholders
34
+ - enhancements: format cache, reduced global namespace pollution
35
+
36
+ 2010.05.22 - 0.6:
37
+ - reverted to 0.4 and fixed the bug regarding the sign of the number 0
38
+ Note:
39
+ Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
40
+ who warned me about a bug in 0.5, I discovered that the last update was
41
+ a regress. I appologize for that.
42
+
43
+ 2010.05.09 - 0.5:
44
+ - bug fix: 0 is now preceeded with a + sign
45
+ - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
46
+ - switched from GPL to BSD license
47
+
48
+ 2007.10.21 - 0.4:
49
+ - unit test and patch (David Baird)
50
+
51
+ 2007.09.17 - 0.3:
52
+ - bug fix: no longer throws exception on empty paramenters (Hans Pufal)
53
+
54
+ 2007.09.11 - 0.2:
55
+ - feature: added argument swapping
56
+
57
+ 2007.04.03 - 0.1:
58
+ - initial release
59
+ **/
60
+
61
+ var sprintf = (function() {
62
+ function get_type(variable) {
63
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
64
+ }
65
+ function str_repeat(input, multiplier) {
66
+ for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
67
+ return output.join('');
68
+ }
69
+
70
+ var str_format = function() {
71
+ if (!str_format.cache.hasOwnProperty(arguments[0])) {
72
+ str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
73
+ }
74
+ return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
75
+ };
76
+
77
+ str_format.format = function(parse_tree, argv) {
78
+ var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
79
+ for (i = 0; i < tree_length; i++) {
80
+ node_type = get_type(parse_tree[i]);
81
+ if (node_type === 'string') {
82
+ output.push(parse_tree[i]);
83
+ }
84
+ else if (node_type === 'array') {
85
+ match = parse_tree[i]; // convenience purposes only
86
+ if (match[2]) { // keyword argument
87
+ arg = argv[cursor];
88
+ for (k = 0; k < match[2].length; k++) {
89
+ if (!arg.hasOwnProperty(match[2][k])) {
90
+ throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
91
+ }
92
+ arg = arg[match[2][k]];
93
+ }
94
+ }
95
+ else if (match[1]) { // positional argument (explicit)
96
+ arg = argv[match[1]];
97
+ }
98
+ else { // positional argument (implicit)
99
+ arg = argv[cursor++];
100
+ }
101
+
102
+ if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
103
+ throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
104
+ }
105
+ switch (match[8]) {
106
+ case 'b': arg = arg.toString(2); break;
107
+ case 'c': arg = String.fromCharCode(arg); break;
108
+ case 'd': arg = parseInt(arg, 10); break;
109
+ case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
110
+ case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
111
+ case 'o': arg = arg.toString(8); break;
112
+ case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
113
+ case 'u': arg = Math.abs(arg); break;
114
+ case 'x': arg = arg.toString(16); break;
115
+ case 'X': arg = arg.toString(16).toUpperCase(); break;
116
+ }
117
+ arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
118
+ pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
119
+ pad_length = match[6] - String(arg).length;
120
+ pad = match[6] ? str_repeat(pad_character, pad_length) : '';
121
+ output.push(match[5] ? arg + pad : pad + arg);
122
+ }
123
+ }
124
+ return output.join('');
125
+ };
126
+
127
+ str_format.cache = {};
128
+
129
+ str_format.parse = function(fmt) {
130
+ var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
131
+ while (_fmt) {
132
+ if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
133
+ parse_tree.push(match[0]);
134
+ }
135
+ else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
136
+ parse_tree.push('%');
137
+ }
138
+ else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
139
+ if (match[2]) {
140
+ arg_names |= 1;
141
+ var field_list = [], replacement_field = match[2], field_match = [];
142
+ if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
143
+ field_list.push(field_match[1]);
144
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
145
+ if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
146
+ field_list.push(field_match[1]);
147
+ }
148
+ else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
149
+ field_list.push(field_match[1]);
150
+ }
151
+ else {
152
+ throw('[sprintf] huh?');
153
+ }
154
+ }
155
+ }
156
+ else {
157
+ throw('[sprintf] huh?');
158
+ }
159
+ match[2] = field_list;
160
+ }
161
+ else {
162
+ arg_names |= 2;
163
+ }
164
+ if (arg_names === 3) {
165
+ throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
166
+ }
167
+ parse_tree.push(match);
168
+ }
169
+ else {
170
+ throw('[sprintf] huh?');
171
+ }
172
+ _fmt = _fmt.substring(match[0].length);
173
+ }
174
+ return parse_tree;
175
+ };
176
+
177
+ return str_format;
178
+ })();
179
+
180
+ var vsprintf = function(fmt, argv) {
181
+ argv.unshift(fmt);
182
+ return sprintf.apply(null, argv);
183
+ };