scratch – Blame information for rev 127
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
75 | office | 1 | |
2 | Pattern = require './Pattern' |
||
3 | |||
4 | # A bunch of utility methods |
||
5 | # |
||
6 | class Utils |
||
7 | |||
8 | @REGEX_LEFT_TRIM_BY_CHAR: {} |
||
9 | @REGEX_RIGHT_TRIM_BY_CHAR: {} |
||
10 | @REGEX_SPACES: /\s+/g |
||
11 | @REGEX_DIGITS: /^\d+$/ |
||
12 | @REGEX_OCTAL: /[^0-7]/gi |
||
13 | @REGEX_HEXADECIMAL: /[^a-f0-9]/gi |
||
14 | |||
15 | # Precompiled date pattern |
||
16 | @PATTERN_DATE: new Pattern '^'+ |
||
17 | '(?<year>[0-9][0-9][0-9][0-9])'+ |
||
18 | '-(?<month>[0-9][0-9]?)'+ |
||
19 | '-(?<day>[0-9][0-9]?)'+ |
||
20 | '(?:(?:[Tt]|[ \t]+)'+ |
||
21 | '(?<hour>[0-9][0-9]?)'+ |
||
22 | ':(?<minute>[0-9][0-9])'+ |
||
23 | ':(?<second>[0-9][0-9])'+ |
||
24 | '(?:\.(?<fraction>[0-9]*))?'+ |
||
25 | '(?:[ \t]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)'+ |
||
26 | '(?::(?<tz_minute>[0-9][0-9]))?))?)?'+ |
||
27 | '$', 'i' |
||
28 | |||
29 | # Local timezone offset in ms |
||
30 | @LOCAL_TIMEZONE_OFFSET: new Date().getTimezoneOffset() * 60 * 1000 |
||
31 | |||
32 | # Trims the given string on both sides |
||
33 | # |
||
34 | # @param [String] str The string to trim |
||
35 | # @param [String] _char The character to use for trimming (default: '\\s') |
||
36 | # |
||
37 | # @return [String] A trimmed string |
||
38 | # |
||
39 | @trim: (str, _char = '\\s') -> |
||
40 | regexLeft = @REGEX_LEFT_TRIM_BY_CHAR[_char] |
||
41 | unless regexLeft? |
||
42 | @REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp '^'+_char+''+_char+'*' |
||
43 | regexLeft.lastIndex = 0 |
||
44 | regexRight = @REGEX_RIGHT_TRIM_BY_CHAR[_char] |
||
45 | unless regexRight? |
||
46 | @REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp _char+''+_char+'*$' |
||
47 | regexRight.lastIndex = 0 |
||
48 | return str.replace(regexLeft, '').replace(regexRight, '') |
||
49 | |||
50 | |||
51 | # Trims the given string on the left side |
||
52 | # |
||
53 | # @param [String] str The string to trim |
||
54 | # @param [String] _char The character to use for trimming (default: '\\s') |
||
55 | # |
||
56 | # @return [String] A trimmed string |
||
57 | # |
||
58 | @ltrim: (str, _char = '\\s') -> |
||
59 | regexLeft = @REGEX_LEFT_TRIM_BY_CHAR[_char] |
||
60 | unless regexLeft? |
||
61 | @REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp '^'+_char+''+_char+'*' |
||
62 | regexLeft.lastIndex = 0 |
||
63 | return str.replace(regexLeft, '') |
||
64 | |||
65 | |||
66 | # Trims the given string on the right side |
||
67 | # |
||
68 | # @param [String] str The string to trim |
||
69 | # @param [String] _char The character to use for trimming (default: '\\s') |
||
70 | # |
||
71 | # @return [String] A trimmed string |
||
72 | # |
||
73 | @rtrim: (str, _char = '\\s') -> |
||
74 | regexRight = @REGEX_RIGHT_TRIM_BY_CHAR[_char] |
||
75 | unless regexRight? |
||
76 | @REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp _char+''+_char+'*$' |
||
77 | regexRight.lastIndex = 0 |
||
78 | return str.replace(regexRight, '') |
||
79 | |||
80 | |||
125 | office | 81 | # Checks if the given value is empty (null, undefined, empty string, string '0', empty Array, empty Object) |
75 | office | 82 | # |
83 | # @param [Object] value The value to check |
||
84 | # |
||
85 | # @return [Boolean] true if the value is empty |
||
86 | # |
||
87 | @isEmpty: (value) -> |
||
125 | office | 88 | return not(value) or value is '' or value is '0' or (value instanceof Array and value.length is 0) or @isEmptyObject(value) |
75 | office | 89 | |
125 | office | 90 | # Checks if the given value is an empty object |
91 | # |
||
92 | # @param [Object] value The value to check |
||
93 | # |
||
94 | # @return [Boolean] true if the value is empty and is an object |
||
95 | # |
||
96 | @isEmptyObject: (value) -> |
||
97 | return value instanceof Object and (k for own k of value).length is 0 |
||
75 | office | 98 | |
99 | # Counts the number of occurences of subString inside string |
||
100 | # |
||
101 | # @param [String] string The string where to count occurences |
||
102 | # @param [String] subString The subString to count |
||
103 | # @param [Integer] start The start index |
||
104 | # @param [Integer] length The string length until where to count |
||
105 | # |
||
106 | # @return [Integer] The number of occurences |
||
107 | # |
||
108 | @subStrCount: (string, subString, start, length) -> |
||
109 | c = 0 |
||
125 | office | 110 | |
75 | office | 111 | string = '' + string |
112 | subString = '' + subString |
||
125 | office | 113 | |
75 | office | 114 | if start? |
115 | string = string[start..] |
||
116 | if length? |
||
117 | string = string[0...length] |
||
125 | office | 118 | |
75 | office | 119 | len = string.length |
120 | sublen = subString.length |
||
121 | for i in [0...len] |
||
122 | if subString is string[i...sublen] |
||
123 | c++ |
||
124 | i += sublen - 1 |
||
125 | office | 125 | |
75 | office | 126 | return c |
127 | |||
128 | |||
129 | # Returns true if input is only composed of digits |
||
130 | # |
||
131 | # @param [Object] input The value to test |
||
132 | # |
||
133 | # @return [Boolean] true if input is only composed of digits |
||
134 | # |
||
135 | @isDigits: (input) -> |
||
136 | @REGEX_DIGITS.lastIndex = 0 |
||
137 | return @REGEX_DIGITS.test input |
||
138 | |||
139 | |||
140 | # Decode octal value |
||
141 | # |
||
142 | # @param [String] input The value to decode |
||
143 | # |
||
144 | # @return [Integer] The decoded value |
||
145 | # |
||
146 | @octDec: (input) -> |
||
147 | @REGEX_OCTAL.lastIndex = 0 |
||
148 | return parseInt((input+'').replace(@REGEX_OCTAL, ''), 8) |
||
149 | |||
150 | |||
151 | # Decode hexadecimal value |
||
152 | # |
||
153 | # @param [String] input The value to decode |
||
154 | # |
||
155 | # @return [Integer] The decoded value |
||
156 | # |
||
157 | @hexDec: (input) -> |
||
158 | @REGEX_HEXADECIMAL.lastIndex = 0 |
||
159 | input = @trim(input) |
||
160 | if (input+'')[0...2] is '0x' then input = (input+'')[2..] |
||
161 | return parseInt((input+'').replace(@REGEX_HEXADECIMAL, ''), 16) |
||
162 | |||
163 | |||
164 | # Get the UTF-8 character for the given code point. |
||
165 | # |
||
166 | # @param [Integer] c The unicode code point |
||
167 | # |
||
168 | # @return [String] The corresponding UTF-8 character |
||
169 | # |
||
170 | @utf8chr: (c) -> |
||
171 | ch = String.fromCharCode |
||
172 | if 0x80 > (c %= 0x200000) |
||
173 | return ch(c) |
||
174 | if 0x800 > c |
||
175 | return ch(0xC0 | c>>6) + ch(0x80 | c & 0x3F) |
||
176 | if 0x10000 > c |
||
177 | return ch(0xE0 | c>>12) + ch(0x80 | c>>6 & 0x3F) + ch(0x80 | c & 0x3F) |
||
178 | |||
179 | return ch(0xF0 | c>>18) + ch(0x80 | c>>12 & 0x3F) + ch(0x80 | c>>6 & 0x3F) + ch(0x80 | c & 0x3F) |
||
180 | |||
181 | |||
182 | # Returns the boolean value equivalent to the given input |
||
183 | # |
||
184 | # @param [String|Object] input The input value |
||
185 | # @param [Boolean] strict If set to false, accept 'yes' and 'no' as boolean values |
||
186 | # |
||
187 | # @return [Boolean] the boolean value |
||
188 | # |
||
189 | @parseBoolean: (input, strict = true) -> |
||
190 | if typeof(input) is 'string' |
||
191 | lowerInput = input.toLowerCase() |
||
192 | if not strict |
||
193 | if lowerInput is 'no' then return false |
||
194 | if lowerInput is '0' then return false |
||
195 | if lowerInput is 'false' then return false |
||
196 | if lowerInput is '' then return false |
||
197 | return true |
||
198 | return !!input |
||
199 | |||
200 | |||
201 | |||
202 | # Returns true if input is numeric |
||
203 | # |
||
204 | # @param [Object] input The value to test |
||
205 | # |
||
206 | # @return [Boolean] true if input is numeric |
||
207 | # |
||
208 | @isNumeric: (input) -> |
||
209 | @REGEX_SPACES.lastIndex = 0 |
||
210 | return typeof(input) is 'number' or typeof(input) is 'string' and !isNaN(input) and input.replace(@REGEX_SPACES, '') isnt '' |
||
211 | |||
212 | |||
213 | # Returns a parsed date from the given string |
||
214 | # |
||
215 | # @param [String] str The date string to parse |
||
216 | # |
||
217 | # @return [Date] The parsed date or null if parsing failed |
||
218 | # |
||
219 | @stringToDate: (str) -> |
||
220 | unless str?.length |
||
221 | return null |
||
222 | |||
223 | # Perform regular expression pattern |
||
224 | info = @PATTERN_DATE.exec str |
||
225 | unless info |
||
226 | return null |
||
227 | |||
228 | # Extract year, month, day |
||
229 | year = parseInt info.year, 10 |
||
230 | month = parseInt(info.month, 10) - 1 # In javascript, january is 0, february 1, etc... |
||
231 | day = parseInt info.day, 10 |
||
232 | |||
233 | # If no hour is given, return a date with day precision |
||
234 | unless info.hour? |
||
235 | date = new Date Date.UTC(year, month, day) |
||
236 | return date |
||
237 | |||
238 | # Extract hour, minute, second |
||
239 | hour = parseInt info.hour, 10 |
||
240 | minute = parseInt info.minute, 10 |
||
241 | second = parseInt info.second, 10 |
||
242 | |||
243 | # Extract fraction, if given |
||
244 | if info.fraction? |
||
245 | fraction = info.fraction[0...3] |
||
246 | while fraction.length < 3 |
||
247 | fraction += '0' |
||
248 | fraction = parseInt fraction, 10 |
||
249 | else |
||
250 | fraction = 0 |
||
251 | |||
252 | # Compute timezone offset if given |
||
253 | if info.tz? |
||
254 | tz_hour = parseInt info.tz_hour, 10 |
||
255 | if info.tz_minute? |
||
256 | tz_minute = parseInt info.tz_minute, 10 |
||
257 | else |
||
258 | tz_minute = 0 |
||
259 | |||
260 | # Compute timezone delta in ms |
||
261 | tz_offset = (tz_hour * 60 + tz_minute) * 60000 |
||
262 | if '-' is info.tz_sign |
||
263 | tz_offset *= -1 |
||
264 | |||
265 | # Compute date |
||
266 | date = new Date Date.UTC(year, month, day, hour, minute, second, fraction) |
||
267 | if tz_offset |
||
125 | office | 268 | date.setTime date.getTime() - tz_offset |
75 | office | 269 | |
270 | return date |
||
271 | |||
272 | |||
273 | # Repeats the given string a number of times |
||
274 | # |
||
275 | # @param [String] str The string to repeat |
||
276 | # @param [Integer] number The number of times to repeat the string |
||
277 | # |
||
278 | # @return [String] The repeated string |
||
279 | # |
||
280 | @strRepeat: (str, number) -> |
||
281 | res = '' |
||
282 | i = 0 |
||
283 | while i < number |
||
284 | res += str |
||
285 | i++ |
||
286 | return res |
||
287 | |||
288 | |||
289 | # Reads the data from the given file path and returns the result as string |
||
290 | # |
||
291 | # @param [String] path The path to the file |
||
292 | # @param [Function] callback A callback to read file asynchronously (optional) |
||
293 | # |
||
294 | # @return [String] The resulting data as string |
||
295 | # |
||
296 | @getStringFromFile: (path, callback = null) -> |
||
297 | xhr = null |
||
298 | if window? |
||
299 | if window.XMLHttpRequest |
||
300 | xhr = new XMLHttpRequest() |
||
301 | else if window.ActiveXObject |
||
302 | for name in ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"] |
||
303 | try |
||
304 | xhr = new ActiveXObject(name) |
||
305 | |||
306 | if xhr? |
||
307 | # Browser |
||
308 | if callback? |
||
309 | # Async |
||
310 | xhr.onreadystatechange = -> |
||
311 | if xhr.readyState is 4 |
||
312 | if xhr.status is 200 or xhr.status is 0 |
||
313 | callback(xhr.responseText) |
||
314 | else |
||
315 | callback(null) |
||
316 | xhr.open 'GET', path, true |
||
317 | xhr.send null |
||
125 | office | 318 | |
75 | office | 319 | else |
320 | # Sync |
||
321 | xhr.open 'GET', path, false |
||
322 | xhr.send null |
||
323 | |||
324 | if xhr.status is 200 or xhr.status == 0 |
||
325 | return xhr.responseText |
||
326 | |||
327 | return null |
||
328 | else |
||
329 | # Node.js-like |
||
330 | req = require |
||
331 | fs = req('fs') # Prevent browserify from trying to load 'fs' module |
||
332 | if callback? |
||
333 | # Async |
||
334 | fs.readFile path, (err, data) -> |
||
335 | if err |
||
336 | callback null |
||
337 | else |
||
338 | callback String(data) |
||
339 | |||
340 | else |
||
341 | # Sync |
||
342 | data = fs.readFileSync path |
||
343 | if data? |
||
344 | return String(data) |
||
345 | return null |
||
346 | |||
347 | |||
348 | |||
349 | module.exports = Utils |