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