///
module TDev.RT {
//? A piece of text
//@ stem("s") icon("ABC") immutable isData builtin ctx(general,indexkey,cloudfield,walltap,enumerable,json)
export module String_
{
//? Returns a string collection that contains the substrings in this string that are delimited by elements of a specified string.
//@ [separator].defl(",")
//@ [result].writesMutable
//@ robust
export function split(self:string, separator:string) : Collection { return Collection.mkStrings(self.split(separator)); }
export function valueFromKeyUrl(url: string) {
if (/^apikey:/i.test(url))
return Web.url_decode(url.substring("apikey:".length));
return null;
}
export function valueToKeyUrl(value: string) {
return "apikey:" + Web.url_encode(value);
}
export function valueFromArtUrl(url: string) {
var m = /^data:[^,]*base64,/i.exec(url)
if (m)
return Web.base64_decode(url.substring(m[0].length));
return null;
}
export function valueToArtUrl(value: string) {
return "data:text/plain;base64," + Web.base64_encode(value);
}
export function fromArtUrl(url: string) {
var value = valueFromArtUrl(url);
if (value) return Promise.wrap(value);
var key = valueFromKeyUrl(url);
if (key) return ApiManager.getKeyAsync(key);
return ArtCache.getArtAsync(url)
.then(dataUrl => {
if (dataUrl) return Promise.as(valueFromArtUrl(dataUrl));
else Util.httpGetTextAsync(url)
})
.then(txt => txt, e => {
App.logEvent(App.ERROR, "art", lf("failed to load url {0}", url), undefined);
return ""
});
}
//? Trims the string at the given length and adds ``...`` if necessary
//@ [lim].defl(140)
export function trim_overflow(self: string, lim: number): string {
var v = self;
if (v && v.length > lim)
v = v.slice(0, lim) + "...";
return v;
}
//? Displays string on the wall
export function post_to_wall(self:string, s:IStackFrame)
{
// backdoor for session testing
if (dbg && self != null && self.indexOf("magic trap ") == 0) {
var tests = new Revisions.SessionTests(s.rt);
tests.runtest(self.substr(11));
return;
}
if (self != null) {
if (s.rt.onCssPage())
s.rt.postUnboxedText(self, s.pc);
else
s.rt.postBoxedTextWithTap(self, self, s.pc);
}
}
//? Returns the number of characters
//@ robust
export function count(self:string):number { return self.length; }
//? Gets the charecter unicode value at a given index. Returns NaN if out of bounds
export function code_at(self: string, index: number): number { return self.charCodeAt(index); }
//? Gets the character at a specified index. Returns invalid if out of bounds.
export function at(self:string, index:number):string { return index < 0 || index >= self.length ? undefined : self.charAt(index); }
//? Returns a copy of this string converted to lowercase, using the casing rules of the current culture.
//@ robust
export function to_lower_case(self:string) : string { return self.toLocaleLowerCase(); }
//? Returns a copy of this string converted to uppercase, using the casing rules of the current culture.
//@ robust
export function to_upper_case(self:string) : string { return self.toLocaleUpperCase(); }
//? Converts a single character string into its unicode number
export function to_unicode(self:string) : number { return self.length == 1 ? self.charCodeAt(0) : undefined; }
//? Compares two pieces of text
//@ robust
export function compare(self:string, other:string) : number
{
var r = self.localeCompare(other);
if (r < 0) return -1;
if (r > 0) return 1;
return 0;
}
//? Concatenates two pieces of text
//@ robust
export function concat(self:string, other:string) : string { return self + other; }
//? Concatenates two pieces of text
//@ name("\u2225") infixPriority(6)
//@ robust
export function concat_op(self:string, other:string) : string { return self + other; }
//@ robust
export function concatAny(a:any, b:any) : string
{
function toStr(v:any) {
if (v === undefined || v === null) return "(invalid)";
if (typeof v == "string") return v;
if (typeof v == "JsonObject") return v.toString();
if (v.to_string) return v.to_string();
return v + "";
}
return toStr(a) + toStr(b);
}
//? Returns a value indicating if the second string is contained
//@ robust
export function contains(self:string, value:string) : boolean { return self.indexOf(value) > -1; }
//? Checks if two strings are the same
//@ robust
export function equals(self:string, other:string) : boolean { return self == other; }
//? Determines whether the ending matches the specified string
//@ robust
export function ends_with(self:string, value:string) : boolean
{
var i = self.lastIndexOf(value);
return i > -1 && i == (self.length - value.length); // TODO: more efficient implementation
}
//? Returns the index of the first occurence if found starting at a given position
//@ robust
export function index_of(self:string, value:string, start:number) : number { return self.indexOf(value, start); }
//? Inserts a string at a given position
export function insert(self: string, start: number, value: string): string
{
if (!value) return self;
if (start < 0 || start > self.length) return undefined;
return self.slice(0, start) + value + self.slice(start);
}
//? Indicates if the string is empty
//@ robust
export function is_empty(self:string) : boolean { return self.length == 0; }
//? Indicates if the string matches a regular expression
export function is_match_regex(self: string, pattern: string): boolean
{
var rx = new RegExp(pattern, "gm");
return rx.test(self);
}
//? Returns the index of the last occurence if found starting at a given position
//@ robust
export function last_index_of(self:string, value:string, start:number) : number { return self.lastIndexOf(value, start); }
//? Gets the groups from the matching the regex expression (pattern)
export function match(self: string, pattern: string): Collection
{
try {
var rx = new RegExp(pattern, "m");
var r = rx.exec(self);
if (!r)
return Collections.create_string_collection();
return Collection.mkStrings(Util.toArray(r));
}
catch (e) {
Time.log('invalid regex pattern: ' + pattern);
return Collections.create_string_collection();
}
}
//? Gets the strings matching the regex expression (pattern)
export function matches(self: string, pattern: string): Collection
{
try {
var rx = new RegExp(pattern, "gm");
var r = self.match(rx);
return Collection.mkStrings(r || []);
}
catch (e) {
Time.log('invalid regex pattern: ' + pattern);
return Collections.create_string_collection();
}
}
//? Returns the string with characters removed starting at a given index
//@ robust
export function remove(self:string, start:number) : string { return self.slice(0, start); }
//? Returns a given string with a replacement
//@ robust
export function replace(self: string, old: string, new_: string): string {
if (!old) return self;
return self.split(old).join(new_);
}
//? Replace every match of the regex according to the replacement string
export function replace_regex(self: string, pattern: string, replace: string): string
{
try
{
var rx = new RegExp(pattern, "gm");
return self.replace(rx, replace);
}
catch (e)
{
Time.log('invalid regex pattern: ' + pattern);
return undefined;
}
}
//? Run `replacer` on every match of the regex
export function replace_regex_with_converter(self: string, pattern: string, replace: StringConverter>, s:IStackFrame): string
{
try
{
var rx = new RegExp(pattern, "gm");
}
catch (e)
{
Time.log('invalid regex pattern: ' + pattern);
return undefined;
}
return self.replace(rx, (...args:string[]) =>
s.rt.runUserAction(replace, [Collection.fromArray(args, "string")]))
}
//? Determines whether the beginning matches the specified string
export function starts_with(self:string, value:string) : boolean {
return self.indexOf(value) == 0; // TODO: more efficient implementation
}
//? Returns a substring given a start index and a length
export function substring(self:string, start:number, length:number) : string { return self.substr(start, length); }
//? Removes all leading and trailing occurrences of a set of characters specified in a string from the current string.
//@ [chars].defl(" \t")
//@ robust
export function trim(self: string, chars: string): string
{
if (!self || !chars) return self;
return trim_start(trim_end(self, chars), chars);
}
//? Removes all leading occurrences of a set of characters specified in a string from the current string.
//@ [chars].defl(" \t")
//@ robust
export function trim_start(self:string, chars:string) : string
{
if (!self || !chars) return self;
var i = 0;
for(; i < self.length && chars.indexOf(self[i]) > -1; i++) {}
return self.substr(i);
}
//? Removes all trailing occurrences of a set of characters specified in a string from the current string.
//@ [chars].defl(" \t")
//@ robust
export function trim_end(self: string, chars: string): string
{
if (!self || !chars) return self;
var i = self.length;
for(; i > 0 && chars.indexOf(self[i-1]) > -1; i--) {}
return self.substring(0, i);
}
//? Parses the string as a time (12:30:12) and returns the number of seconds.
export function to_time(self: string): number
{
if (self != null) {
var s = trim(self, ' \t\n\r');
var m = s.match(/(\d{1,2})\s*:\s*(\d{1,2})\s*(:\s*(\d{1,2}))?\s*(pm|am)?/i);
if (m) {
var hours = parseFloat(m[1]);
if (m[5] && /pm/i.test(m[5])) hours += 12;
var minutes = parseFloat(m[2]);
var seconds = m[4] ? parseFloat(m[4]) : 0;
return Math_.clamp(0, 23, hours) * 3600 + Math_.clamp(0, 59, minutes) * 60 + seconds;
}
}
return undefined;
}
//? Parses the string as a number
export function to_number(self:string) : number
{
// TODO: localization
if (/^\s*[-+]?\d*\.?(\d+([eE][-+]?\d+)?)?\s*$/.test(self)) {
var r = parseFloat(self);
if (isNaN(r)) return undefined;
return r;
} else {
return undefined;
}
}
//? Parses the string as a boolean
export function to_boolean(self:string) : boolean { return self.trim().toLocaleLowerCase() == "true"; }
//? Parses the string as a geo coordinate.
export function to_location(self: string): Location_
{
var s = trim_start(trim_end(self, ")}] "), "([{ ");
var seps = ',;:';
for (var i = 0; i < seps.length; ++i) {
var index = s.indexOf(seps[i]);
if (index > -1) {
var lat = to_number(s.substring(0, index));
var long = to_number(s.substring(index + 1));
if (lat && long && !isNaN(lat) && !isNaN(long)) {
return Location_.mkShort(lat, long);
}
}
}
return undefined;
}
//? Shares the string (email, sms, facebook, social or '' to pick from a list)
//@ uiAsync flow(SinkSharing)
//@ [network].defl("social")
export function share(self: string, network: string, r : ResumeCtx): void
{
HTML.showProgressNotification(lf("sharing text..."));
ShareManager.shareTextAsync(self, network)
.done(() => r.resume());
}
// Stores text in the clipboard
//? Stores text in the clipboard
//@ flow(SinkClipboard) uiAsync
export function copy_to_clipboard(self:string, r : ResumeCtx) : void
{
ShareManager.copyToClipboardAsync(self).done(() => r.resume());
}
//? Parses the string as a date and time.
export function to_datetime(self:string) : DateTime { return DateTime.parse(self); }
//? Parses the string as a color.
export function to_color(self: string): Color { return Color.fromHtml(self); }
export function picker()
{
var inp = HTML.mkTextInput("text", lf("color"));
return {
html: inp,
validate: () => true,
get: () => inp.value,
set: function(v) { inp.value = v + "" }
};
}
//? Converts the value into a json data structure.
export function to_json(self: string): JsonObject {
return JsonObject.wrap(self);
}
}
}