I'm having an interesting time trying to get my Pebble watch to honor the escape sequence character \t when pushing data to my watch (using SimplyJS).
The following snippet is the code that I have been using:
simply.scrollable(true);
simply.style('small');
simply.fullscreen(true);
var aflLadderUrl = 'http://www.sportal.com.au/feeds/sss/afl_ladder.json';
var ladderContent = '';
ajax({ url: aflLadderUrl, type: 'json'},
function(data) {
var i = 0;
while (i < 18){
ladderContent = ladderContent + data.ladder[i].friendly_name + '\t\t\t' + data.ladder[i].percentage + '\n';
i++;
}
simply.text({
title: 'AFL Ladder',
body: ladderContent
});
},
function() {
simply.text({
title: 'AFL Ladder',
body: 'No internet connection'
});
}
);
What I am currently observing is that the \n is being honored and I can see that on my watch, each row of data is being displayed on a separate line. However, it appears that my \t are being ignored and instead of a tab being inserted into my line, zero whitespace is being displayed (i.e. 'my name is' + '\t\t\t' + 'dave' is displayed as my name isdave).
I have also tried compiling a Hello World program using just the Pebble SDK (taking the code from https://github.com/kristofvc/pebble-hello-world/blob/master/src/pebble_hello_world.c and adding a couple of \t\t in the string to be printed on line 11) and have also noticed that the SDK honors \n characters but not \t characters (just as my SimplyJS app did).
My question is: is it possible to have the Pebble (either via the SDK or SimplyJS) display tabs the same way you'd expect them to work when printing to console? I understand the \t character may not be supported and instead of using \t I could just use spaces, but this one had me curious.
Please let me know if you require any more information.
Thanks in advance!
Because \t is a control (non-printable) character, it doesn't correspond to any glyph and won't get "displayed" consistently, if at all. Terminal emulators will interpret it differently than spreadsheet software reading a TSV file, for example. It sounds like the part of the Pebble's firmware that converts a bytestring to pixels on a display just ignores \t, although I couldn't find this in the SDK documentation.
If you're using a fixed-width font, you can implement your own tabs using spaces, like this:
var data = {ladder: [
{friendly_name: "Cool Team", percentage: 80.0},
{friendly_name: "Really Cool Team", percentage: 80.0},
{friendly_name: "The Coolest Team of All", percentage: 80.0}
]}
var len = function(obj) {return obj.friendly_name.length}
var longer = function(a, b) {return len(a) > len(b) ? a : b}
var longestName = len(data.ladder.reduce(longer))
var tab = function(obj) { return new Array(longestName - len(obj) + 2).join(" ") }
var print = function(obj) { return obj.friendly_name + tab(obj) + obj.percentage }
var ladderContent = data.ladder.map(print).join("\n")
// Cool Team 80
// Really Cool Team 80
// The Coolest Team of All 80
Related
I was wondering how to trim a file name in JS to show "..." or any appendix for that matter after a certain number of characters, the most efficient way to handle all possible test cases.
Rules
Show the actual file extension and not the last character after splitting the string name with "."
The function should take the input file name (string), the number of characters to trim (integer) and appendix (string) as the parameter.
By efficient, I mean I expect to write fewer lines of code and handle all possible edge cases.
Sample Inputs
myAwesomeFile.min.css
my Awesome File.tar.gz
file.png
Expected output (say I want to trim after 5 characters)
myAwe....min.css
my Aw....tar.gz
file.png
Editing the question to show my attempt
function trimFileName(str, noOfChars, appendix) {
let nameArray = str.split(".");
let fileType = `.${nameArray.pop()}`;
let fileName = nameArray.join(" ");
if (fileName.length >= noOfChars) {
fileName = fileName.substr(0, noOfChars) + appendix;
};
return (fileName + fileType);
}
console.log(trimFileName("myAwesomeFile.min.css", 5, "..."));
console.log(trimFileName("my Awesome File.tar.gz", 5, "..."));
console.log(trimFileName("file.png", 5, "..."));
Edit #2: Feel free to go ahead and edit the question if you think it's not the standard expectation and add more edge cases to the sample inputs and expected outputs.
Edit #3: Added a few more details to the question after the new comments. I know my attempt doesn't fulfill my expected outputs (and I am unsure whether the output I have listed above is a standard expectation or not).
Edit #4 (Final): Removed the rule of not breaking a word in the middle after a continuous backlash in the comments and changed the rules to cater to more realistic and practical use cases.
If we treat the dot character . as a separator for file extensions, what you ask for can be solved with a single line of JavaScript:
name.replace(new RegExp('(^[^\\.]{' + chars + '})[^\\.]+'), '$1' + subst);
Demo code in the following snippet:
function f(name, chars, subst) {
return name.replace(
new RegExp('(^[^\\.]{' + chars + '})[^\\.]+'), '$1' + subst);
}
test('myAwesomeFile.min.css', 5, '...', 'myAwe....min.css');
test('my Awesome File.tar.gz', 5, '...', 'my Aw....tar.gz');
test('file.png', 5, '...', 'file.png');
function test(filename, length, subst, expected) {
let actual = f(filename, length, subst);
console.log(actual, actual === expected ? 'OK' : 'expected: ' + expected);
}
On Windows, AFAIK, the file extension is only what follows the last dot. Thus, technically, the file extension of "myAwesomeFile.min.css" is just css, and the file extension of "my Awesome File.tar.gz" is just gz.
In this case, what you ask for can still be solved with one line of JavaScript:
name.replace(new RegExp('(^.{' + chars + '}).+(\\.[^\\.]*$)'), '$1' + subst + '$2');
Demo code in the following snippet:
function f(name, chars, subst) {
return name.replace(
new RegExp('(^.{' + chars + '}).+(\\.[^\\.]*$)'), '$1' + subst + '$2');
}
test('myAwesomeFile.min.css', 5, '...', 'myAwe....css');
test('my Awesome File.tar.gz', 5, '...', 'my Aw....gz');
test('file.png', 5, '...', 'file.png');
function test(filename, length, subst, expected) {
let actual = f(filename, length, subst);
console.log(actual, actual === expected ? 'OK' : 'expected: ' + expected);
}
If you really want to allow edge cases with specific multiple extensions, you probably need to define a comprehensive list of all allowed multiple extensions to know how to deal with cases like "my.awesome.file.min.css". You would need to provide a list of all cases you want to include before it would be possible to determine how efficient any solution could be.
It is really hard to account for all extensions (including edge cases). See this list for example for common extensions: https://www.computerhope.com/issues/ch001789.htm. Event with that many extensions, the list is exhaustive of all extensions.
Your function is OK but to account for more cases it could be re-written to this:
function trimFileName(filename, limit = 5, spacer = '.') {
const split = filename.indexOf(".");
const name = filename.substring(0, split);
const ext = filename.substring(split);
let trimName = name.substring(0, limit);
if (name.length > trimName.length)
trimName = trimName.padEnd(limit + 3, spacer);
return trimName + ext;
}
console.log(trimFileName("myAwesomeFile.min.css"));
console.log(trimFileName("my Awesome File.tar.gz"));
console.log(trimFileName("file.png"));
Below is a pretty simple approach to achieve shortening in the fashion you desire. Comments are in the code, but let me know if anything needs additional explanation:
//A simple object to hold some configs about how we want to shorten the file names
const config = {
charsBeforeTrim: 5,
seperator: '.',
replacementText: '....'
};
//Given file names to shorten
const files = ['myAwesomeFile.min.css', 'my Awesome File.tar.gz', 'file.png'];
//Function to do the actual file name shortening
const shorten = s =>
s.length > config.charsBeforeTrim ? `${s.substring(0, config.charsBeforeTrim)}${config.replacementText}` : s;
//Function to generate a short file name with extension(s)
const generateShortName = (file, config) => {
//ES6 Array destructuring makes it easy to get the file name in a unique variable while keeping the remaining elements (the extensions) in their own array:
const [name, ...extensions] = file.split(config.seperator);
//Simply append all remaining extension chunks to the shortName
return extensions.reduce((accum, extension) => {
accum += `${config.seperator}${extension}`;
return accum;
}, shorten(name));
};
//Demonstrate usage
const shortFileNames = files.map(file => generateShortName(file, config));
console.log(shortFileNames);
const parse = (filename, extIdx = filename.lastIndexOf('.')) => ({
name: filename.substring(0, extIdx),
extension: filename.substring(extIdx + 1),
})
const trimFileName = (
filename, size = 5, fill = '...',
file = parse(filename),
head = file.name.substring(0, size)
) => file.name.length >= size ? `${head}${fill}${file.extension}` : filename
/* - - - - - - - - - - - - - - - - - - - - - - - - - */
;[
'myAwesomeFile.min.css',
'my.Awesome.File.min.css',
'my Awesome File.tar.gz',
'file.png',
].forEach(f => console.log(trimFileName(f)))
You can fairly straightforwardly pull the your extension condition (easily replaced with a list of valid extensions) and regex pull the last part. Then you just add a check on the filename (starting at the beginning of filename) to trim the result.
const trim = (string, x) => {
// We assume that up to last two . delimited substrings of length are the extension
const extensionRegex = /(?:(\.[a-zA-Z0-9]+){0,2})$/g;
const { index } = extensionRegex.exec(string);
// No point in trimming since result string would be longer than input
if (index + 2 < x) {
return string;
}
return string.substr(0, x) + ".." + string.substr(index);
};
/* Assert that we keep the extension */
console.log("cat.tar.gz", trim("cat.tar.gz", 100) == "cat.tar.gz");
console.log("cat.zip", trim("cat.zip", 100) == "cat.zip");
/* Assert that we keep x characters before trim */
console.log("1234567890cat.tar.gz",!trim("1234567890cat.tar.gz",10).includes("cat"));
console.log("1234567890cat.zip", !trim("1234567890cat.zip", 10).includes("cat"));
I have string parser in node.js. Input string comes from telegram channel.
Now I have serious problem with String.split function.
It works with some types of text but it doesn't work with some other texts.
When I receive not processed string in telegram, I just copy and send it in the channel again.
In this case, parser processes it well.
Is there any advise for this issue?
let teams = [];
teamSeps =[" vs ", " v ", " - ", " x " ,"-", " -"];
for(let i = 0; i< teamSeps.length; i++){
teams = newTip.Match.toLowerCase().split(teamSeps[i]);
if(teams.length === 2) break;
}
newTip.Home = teams[0].trim();
newTip.Away = teams[1].trim();
return;
Instead of adding multiple options with optional spaces on either side of -, you can use a single regex with some alternation.
/\s*-\s*|\s+(?:vs|v|x)\s+/
\s*-\s*: Allows optional space around -
\s+(?:vs|v|x)\s+: Allows at least one space around vs or v or x (Otherwise, if there is a x or v in the string, it will split)
function customSplit(str) {
return str.split(/\s*-\s*|\s+(?:vs|v|x)\s+/);
}
console.log(customSplit("Man United vs Man City"))
console.log(customSplit("France - Croatia"))
console.log(customSplit("Belgium-England"))
console.log(customSplit("Liverpool x Spurs"))
I'm building a Javascript chat bot for something, and I ran into an issue:
I use string.split() to tokenize my input like this:
tokens = message.split(" ");
Now my problem is that I need 4 tokens to make the command, and 1 token to have a message.
when I do this:
!finbot msg testuser 12345 Hello sir, this is a test message
these are the tokens I get:
["!finbot", "msg", "testuser", "12345", "Hello", "sir,", "this", "is", "a", "test", "message"]
However, how can I make it that it will be like this:
["!finbot", "msg", "testuser", "12345", "Hello sir, this is a test message"]
The reason I want it like this is because the first token (token[0]) is the call, the second (token[1]) is the command, the third (token[2]) is the user, the fourth (token[3]) is the password (as it's a password protected message thing... just for fun) and the fifth (token[4]) is the actual message.
Right now, it would just send Hello because I only use the 5th token.
the reason why I can't just go like message = token[4] + token[5]; etc. is because messages are not always exactly 3 words, or not exactly 4 words etc.
I hope I gave enough information for you to help me.
If you guys know the answer (or know a better way to do this) please tell me so.
Thanks!
Use the limit parameter of String.split:
tokens = message.split(" ", 4);
From there, you just need to get the message from the string. Reusing this answer for its nthIndex() function, you can get the index of the 4th occurrence of the space character, and take whatever comes after it.
var message = message.substring(nthIndex(message, ' ', 4))
Or if you need it in your tokens array:
tokens[4] = message.substring(nthIndex(message, ' ', 4))
I would probably start by taking the string like you did, and tokenizing it:
const myInput = string.split(" "):
If you're using JS ES6, you should be able to do something like:
const [call, command, userName, password, ...messageTokens] = myInput;
const message = messageTokens.join(" ");
However, if you don't have access to the spread operator, you can do the same like this (it's just much more verbose):
const call = myInput.shift();
const command = myInput.shift();
const userName = myInput.shift();
const password = myInput.shift();
const message = myInput.join(" ");
If you need them as an array again, now you can just join those parts:
const output = [call, command, userName, password, message];
If you can use es6 you can do:
let [c1, c2, c3, c4, ...rest] = input.split (" ");
let msg = rest.join (" ");
You could revert to regexp given that you defined your format as "4 tokens of not-space separated with spaces followed by message":
function tokenize(msg) {
return (/^(\S+) (\S+) (\S+) (\S+) (.*)$/.exec(msg) || []).slice(1, 6);
}
This has the perhaps unwanted behaviour of returning an empty array if your msg does not actually match the spec. Remove the ... || [] and handle accordingly, if that's not acceptable. The amount of tokens is also fixed to 4 + the required message. For a more generic approach you could:
function tokenizer(msg, nTokens) {
var token = /(\S+)\s*/g, tokens = [], match;
while (nTokens && (match = token.exec(msg))) {
tokens.push(match[1]);
nTokens -= 1; // or nTokens--, whichever is your style
}
if (nTokens) {
// exec() returned null, could not match enough tokens
throw new Error('EOL when reading tokens');
}
tokens.push(msg.slice(token.lastIndex));
return tokens;
}
This uses the global feature of regexp objects in Javascript to test against the same string repeatedly and uses the lastIndex property to slice after the last matched token for the rest.
Given
var msg = '!finbot msg testuser 12345 Hello sir, this is a test message';
then
> tokenizer(msg, 4)
[ '!finbot',
'msg',
'testuser',
'12345',
'Hello sir, this is a test message' ]
> tokenizer(msg, 3)
[ '!finbot',
'msg',
'testuser',
'12345 Hello sir, this is a test message' ]
> tokenizer(msg, 2)
[ '!finbot',
'msg',
'testuser 12345 Hello sir, this is a test message' ]
Note that an empty string will always be appended to returned array, even if the given message string contains only tokens:
> tokenizer('asdf', 1)
[ 'asdf', '' ] // An empty "message" at the end
Google form checkbox question combines the selected responses into one column separated by commas. I am trying to send out a confirmation email of these responses, but the email results in a clunk format:
Register: 9:15 AM - 10:00 AM Instructional Technology Tools & Resources (electronic lab notebooks, classroom tech, analytics, etc.), 10:10 AM - 11:00 AM Tools for flipped or hybrid (blended) courses (learning glass, instructional design, Ted, podcast, etc.)
I'd like to replace the comma separator with <br> but haven't been able to figure it out.
I've tried:
register = e.namedValues[headers[4]].split(',');
register = e.namedValues[headers[4]];
workshops = register.replace(",", "<br>");
Any help would be greatly appreciated! Thank you!
for (var i in headers) {
....... .......
register = e.namedValues[headers[4]];
}
if (e.namedValues[headers[i]].toString() != "" ) {
textbody = ....... + register + "<br>"
}
Should be structured like this:
for (var i in headers) {
....... .......
register = e.namedValues[headers[i]].toString();
if (register != "" ) {
textbody = register.replace(/,/g, "<br>");
}
}
You need to perform a global replacement, after converting the array to a string.
function replaceCommas() {
var headers = ['First Name', 'Timestamp', 'Last Name', 'workshops'];
var register = {'First Name': ['Jane', 'Jon', 'Jim', 'Josie'], 'Timestamp': ['6/7/2015 20:54:13'], 'Last Name': ['Doe'], 'workshops': ['learning glass', 'instructional design', 'Ted', 'podcast']};
//register = e.namedValues[headers[4]].split(',');
var keyName = headers[3]; //Arrays are Zero indexed, fourth item is index 3
Logger.log('keyName is: ' + keyName);
var stringOfValues = register[keyName].toString();
Logger.log('stringOfValues: ' + stringOfValues);
var workshops = stringOfValues.replace(/,/g, "<br>");
Logger.log('workshops: ' + workshops);
}
Copy the above function into your script editor, set a breakpoint on something like the first Logger.log() statement, and step into each line. You can see all the values of the variables in the debug window at the bottom of the screen.
To set a breakpoint, click a line number, and a red dot will appear, that's where the code will stop. Then click the icon of the bug. The code will run up to the point of the breakpoint.
Here is a link to the debug documentation:
Google Documentation - Troubleshooting
I have been working on this most of the morning but to no end. I am trying to execute a button that uses OnClick Java in Salesforce.com and it keeps throwing errors. I think the issue may be with special characters in the data as it works when I simply use just text. But any time numbers or any special characters are present I get the error "unexpected token ILLEGAL". Can anyone help me to see what I am doing wrong and how I can get away from failing when special characters are involved?
{!REQUIRESCRIPT("/soap/ajax/28.0/connection.js")}
var opptyObj = new sforce.SObject("Opportunity");
var caseObj = new sforce.SObject("Case");
var today = new Date();
var sOpptyId = "{!Case.Opportunity__c}";
if( sOpptyId != "")
{
alert("This case is already tied to an opportunity!");
}
else
{
opptyObj.AccountId = "{!Case.AccountId}";
opptyObj.CloseDate = sforce.internal.dateTimeToString(today);
opptyObj.Description="{!Case.Description}";
opptyObj.Case__c = "{!Case.Id}";
opptyObj.Name = "{!Case.Subject}";
opptyObj.StageName = "Estimate in Progress";
opptyObj.Created_from_Case__c = "Y";
opptyObj.Type = "New Business";
opptyObj.Amount = ".01";
var opptyresult = sforce.connection.create([opptyObj]);
if (opptyresult[0].success=='false')
{
alert("Opportunity creation failed: " + opptyresult[0].errors.message);
}
else
{
caseObj.Id = '{!Case.Id}';
caseObj.Opportunity__c = opptyresult[0].id;
caseObj.Status = "Estimate in Progress";
var caseResult = sforce.connection.update([caseObj]);
if(caseResult[0].success == 'false')
{
alert("Case update failed: " + caseResult[0].errors.message);
}
else
{
alert("An opportunity has been created and linked to this case.");
location.reload(true);
}
}
}
Assuming this is some kind of template, whatever is rendering this needs to properly escape some values in the strings it's inserting.
Given this:
opptyObj.Description="{!Case.Description}";
Let's say I enter a description consisting of this:
"That is awesome," said John.
When that is rendered in your template the result is this:
opptyObj.Description=""That is awesome," said John.";
As you might be able to see, the result is a syntax error.
You need to escape quote characters in an text inserted this way. And without knowing what is technology rendering this template I can't give you any specifics, but you want to replace " with \" and ' with \'. The \ escapes characters, forcing them to be treated as literal characters in the string instead of other special meaning.
This must be done as it's being inserted into the script. Something in the spirit of this:
opptyObj.Description="{!Case.Description.replace(/'/, "\\'").replace(/"/, '\\"')}
Exactly how to do that depends on what language or template engine is being used here. But th eresult should look like this:
opptyObj.Description="\"That is awesome,\" said John.";
Ruby on Rails implements an escape_javascript method, which sanitizes data for injection into Javascript. It does the following replacements. It seems like a good baseline.
'\\' => '\\\\'
'</' => '<\/'
"\r\n" => '\n'
"\n" => '\n'
"\r" => '\n'
'"' => '\\"'
"'" => "\\'"
UPDATE:
According to this: http://www.salesforce.com/us/developer/docs/pages/Content/pages_security_tips_scontrols.htm
It looks like you want the JSENCODE function. Something like this, perhaps?
opptyObj.Description="{!JSENCODE(Case.Description)}";