New Text Converter IA (#4335)

* Initial commit.

* Prototype up and running.

* Check in work.

* Update work.

* Checking in work.

* Updates based on feedback.

* Code in design.

* Update padding / margin on controls.

* fix mobile styles
master
PJ Hampton 2017-07-13 22:46:49 +01:00 committed by Zaahir Moolla
parent b9297e004a
commit 588c354075
6 changed files with 666 additions and 0 deletions

View File

@ -0,0 +1,129 @@
package DDG::Goodie::TextConverter;
# ABSTRACT: An Instant Answer to convert between different formats
# eg. ASCII, Binary, hexadecimal, etc
use DDG::Goodie;
use strict;
use warnings;
use List::Util 'max';
use YAML::XS 'LoadFile';
zci answer_type => 'text_converter';
zci is_cached => 0;
my @triggers = ();
my @single_triggers = ();
my @lang_triggers = ();
my %natlang_hash = %{ LoadFile(share('text_converter.yml')) };
my @keys = keys %natlang_hash;
push @single_triggers, (keys %natlang_hash);
while( my( $key, $value ) = each %natlang_hash ){
if($value) {
foreach(@$value) {
push(@triggers, $_);
}
} else {
push(@triggers, $key);
}
}
my @generics = qw/ converter convert conversion translate translator translation encoder encode decoder decode /;
my @merged_triggers = (@single_triggers, @triggers);
my $triggers_re = join "|", @merged_triggers;
my $generics_re = join "|", @generics;
for my $trig (@triggers) {
push @lang_triggers, map { "$trig $_" } @generics;
}
my $guard = qr/^
(?:$generics_re)?\s?
(?<from_type>$triggers_re)\s
(?<connector>to|from|vs|-)\s
(?<to_type>$triggers_re)\s?
(?:$generics_re)?
$
/ix;
triggers any => (
@triggers,
@single_triggers,
@lang_triggers,
);
handle query_lc => sub {
my $query = $_;
my $from_type = "";
my $to_type = "";
# check to see if the query is a command based trigger
# eg. binary to ascii, decimal from ascii, convert binary to ascii
if(m/$guard/) {
$from_type = get_type_information($+{'from_type'});
$to_type = get_type_information($+{'to_type'});
my $connector = $+{'connector'};
# the user wants to convert /from/, we'll switch the types
if ($connector eq 'from') {
($from_type, $to_type) = ($to_type, $from_type);
}
return '',
structured_answer => {
data => {
title => "Text Converter",
from_type => $from_type,
to_type => $to_type
},
templates => {
group => 'text',
}
};
}
# check to see if query is a static language based trigger
# eg. binary converter, hex encoder
if(grep(/^$query$/, @lang_triggers)) {
$to_type = get_type_information($query);
return '',
structured_answer => {
data => {
title => "Text Converter",
from_type => $from_type,
to_type => $to_type
},
templates => {
group => 'text',
}
};
}
return;
};
# checks the /type/ of the query
# eg. binary converter --> binary
sub get_type_information {
my $input = shift;
$input =~ s/$generics_re|\s//gi;
foreach my $key (keys %natlang_hash) {
return $key if $key eq $input;
if($natlang_hash{$key}) {
my @hash_kv = @{$natlang_hash{$key}};
foreach my $value (@hash_kv) {
return $key if $input eq $value;
}
}
}
return 'text'; # default to text
}
1;

View File

@ -0,0 +1,40 @@
<div class="gw">
<div class="g js_minify--wrap frm half">
<textarea name="text-converter--input" id="text-converter--input" class="frm__input text-converter--input whole tx--16 frm__text" placeholder="Text to convert from..."></textarea>
</div>
<div class="g js_minify--wrap fifty frm">
<textarea name="js_minify--output" id="text-converter--output" class="frm__input js_minify--output whole tx--16 frm__text" cols="60" readonly="readonly" onclick="this.focus();this.select()"></textarea>
</div>
</div>
<div id="controls">
<span class="js_convert--select--wrap">
<label for="js_convert--select-from" id="controls--from-label">From:</label><div class="frm__select">
<select name="" id="js_convert--select-from">
<option value="text" selected="selected">ASCII / ANSI</option>
<option value="binary">Binary</option>
<option value="decimal">Decimal</option>
<option value="rot13">Rot13</option>
<option value="base64">Base64</option>
<option value="hexadecimal">Hexadecimal</option>
</select>
</div>
</span>
<span class="js_convert--select--wrap">
<label for="js_convert--select-to" id="controls--to-label">To:</label><div class="frm__select">
<select name="" id="js_convert--select-to">
<option value="text">ASCII / ANSI</option>
<option value="binary">Binary</option>
<option value="decimal">Decimal</option>
<option value="rot13">Rot13</option>
<option value="base64">Base64</option>
<option value="hexadecimal" selected="selected">Hexadecimal</option>
</select>
</div>
</span>
<button class="btn btn--primary" id="js_convert-button">Convert</button>
</div>

View File

@ -0,0 +1,64 @@
/*
* The text converter form box
*/
.zci--text_converter textarea {
background-color: #ffffff;
height: 10em;
}
/*
* Dark theme changes to the text areas
*/
.dark-bg .zci--text_converter textarea {
background-color: #4b4b4b;
border-color: #434343;
color: #c2c2c2;
}
/*
* The text converter controls
*/
.zci--text_converter #controls {
margin: 1em 0 0.3em 0;
}
.zci--text_converter #controls--to-label {
margin-left: .5em;
}
.zci--text_converter #js_convert-button {
margin-left: 1em;
}
/*
* Mobile
*/
.is-mobile .zci--text_converter textarea {
height: 8em;
}
.is-mobile .zci--text_converter #js_convert-button {
margin: 2em auto 0;
width: 100%;
display: block;
box-sizing: border-box;
}
.is-mobile .zci--text_converter .js_convert--select--wrap {
display: block;
width: 100%;
margin: 1em 0;
}
.is-mobile .zci--text_converter .js_convert--select--wrap label {
width: 25%;
display: inline-block;
margin-left: 0 !important;
}
.is-mobile .zci--text_converter .js_convert--select--wrap .frm__select {
width: 75%;
display: inline-block;
}

View File

@ -0,0 +1,259 @@
DDH.text_converter = DDH.text_converter || {};
(function(DDH) {
"use strict";
var initialized = false;
// global DOM objects
var $convert_button,
$convert_selects,
$convert_from_select,
$convert_to_select,
$convert_from_textarea,
$convert_to_textarea;
// TextConverter: A singleton object that performs all the text conversions
var TextConverter = {
convert: function() {
var convert_from = $convert_from_textarea.val();
$convert_to_textarea.val(
TextConverter.converter(convert_from)
)
},
converter: function() {
var from_type = $convert_from_select.val();
var to_type = $convert_to_select.val();
var from = $convert_from_textarea.val();
// We first need to convert the from type to ascii
if(from_type !== "text") {
switch(from_type) {
case "binary":
from = TextConverter.binaryToText(from);
break;
case "decimal":
from = TextConverter.decimalToText(from);
break;
case "rot13":
from = TextConverter.rot13(from);
break;
case "base64":
from = TextConverter.base64Decoder(from);
break;
case "hexadecimal":
from = TextConverter.hexToText(from);
break;
default:
return from; // DEFAULT: return the original text
}
}
// then map it to the /to/ type
switch(to_type) {
case "binary":
return TextConverter.toBinary(from);
break;
case "decimal":
return TextConverter.toDecimal(from);
break;
case "rot13":
return TextConverter.rot13(from);
break;
case "base64":
return TextConverter.base64Encoder(from);
break;
case "hexadecimal":
return TextConverter.toHex(from);
break;
default:
return from; // DEFAULT: return the original text
}
},
/**
* Binary Converter
********************************************
*
* toBinary: text --> binary
* binaryToText: binary --> text
*
*/
toBinary: function( text ) {
var binary = "";
for (var i=0; i < text.length; i++) {
binary += text[i].charCodeAt(0).toString(2) + " ";
}
return binary
},
binaryToText: function( text ) {
return text.split(/\s/).map(function (val) {
return String.fromCharCode(parseInt(val, 2));
}).join("");
},
/**
* Decimal Converters
********************************************
*
* toDecimal: text --> decimal (Base 10)
* decimalToText: decimal --> text
*
*/
toDecimal: function( text ) {
var decimal = "";
for (var i=0; i < text.length; i++) {
decimal += text[i].charCodeAt(0).toString(10) + " ";
}
return decimal;
},
decimalToText: function( text ) {
return text.split(/\s/).map(function (val) {
return String.fromCharCode(parseInt(val, 10));
}).join("");
},
/**
* Rot13 Encoder/Decoder
********************************************
*
* text --> rot13
* rot13 --> text
*
* This is the inversion of itself!
*
*/
rot13: function( input ) {
return input.replace(/[a-zA-Z]/g, function(c) {
return String.fromCharCode(
// this is a ternery statement, and the first arg is also a ternery statement
// step 1: checks to see if character is less than Z then 90, else 122
// step 2: checks the unicode and adds 13
// step 3: check if step 1 result is greater than or equal to step 2
// step 4: return unicode character else unicode character less than 26
(c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c-26 );
});
},
/**
* Base64 Encoder / Decoder
********************************************
*
* base64Encoder: text --> encoded base64
* base64Decoder: encoded base64 --> text
*
*/
base64Encoder: function( input ) {
return window.btoa(input);
},
base64Decoder: function( input ) {
return window.atob(input);
},
/**
* Hex Conversions
********************************************
*
* toHex: text --> hex
* hexToText: hex --> text
*
*/
toHex: function( text ) {
var tmp_array = [];
var pretty_result = "";
for (var i = 0 ; i < text.length ; i++) {
var hex = Number(text.charCodeAt(i)).toString(16);
tmp_array.push(hex);
}
var result = tmp_array.join('');
// this is for presentation for the user only
for ( var j = 0 ; j <= result.length ; j++ ) {
if ( j % 2 ) {
pretty_result += result.charAt(j) + ' ';
} else {
pretty_result += result.charAt(j);
}
}
return pretty_result;
},
hexToText: function( hex ) {
return hex.split(/\s/).map(function (val) {
return String.fromCharCode(parseInt(val, 16));
}).join("");
},
} // TextConverter Obj
var Interface = {
// can't convert two the same of the same class, swaps the labels.
swapSelects: function(prevState, newState, id) {
if(id === "js_convert--select-to") {
if($convert_from_select.val() === newState) {
$convert_from_select.val(prevState);
}
} else {
if($convert_to_select.val() === newState) {
$convert_to_select.val(prevState);
}
}
}
}
DDH.text_converter.build = function(ops) {
var from_type = ops.data.from_type;
var to_type = ops.data.to_type;
return {
templates: {
group: 'text',
options: {
content: "DDH.text_converter.content"
},
},
onShow: function() {
if(initialized) { return } // stops the local dom being rebuilt
var $text_converter = $(".zci--text_converter");
$convert_button = $text_converter.find("#js_convert-button");
$convert_from_select = $text_converter.find("#js_convert--select-from");
$convert_to_select = $text_converter.find("#js_convert--select-to");
$convert_selects = $text_converter.find("select");
$convert_from_textarea = $text_converter.find("#text-converter--input");
$convert_to_textarea = $text_converter.find("#text-converter--output");
// sets the selects
from_type !== "" ? $convert_from_select.val(from_type) : $convert_from_select.val("text");
to_type !== "" ? $convert_to_select.val(to_type) : $convert_to_select.val("text");
// Once clicked, we will convert whatever is in the /from/ textarea
$convert_button.click(TextConverter.convert);
// swaps the selects around if they are the new select is equalilant to the opposite
var previous;
$convert_selects.on('focus', function () {
previous = this.value;
}).change(function() {
Interface.swapSelects(previous, this.value, this.id);
});
initialized = true
}
};
};
})(DDH);

View File

@ -0,0 +1,9 @@
text:
- ascii
- ansi
binary:
decimal:
hexadecimal:
- hex
base64:
rot13:

165
t/TextConverter.t Normal file
View File

@ -0,0 +1,165 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use Test::Deep;
use DDG::Test::Goodie;
zci answer_type => 'text_converter';
zci is_cached => 0;
# Build a structured answer that should match the response from the
# Perl file.
sub build_structured_answer {
my ($input) = @_;
return 'plain text response',
structured_answer => {
data => {
title => 'Text Converter',
from_type => $input->{'from_type'},
to_type => $input->{'to_type'}
},
templates => {
group => 'text',
}
};
}
# Use this to build expected results for your tests.
sub build_test { test_zci(build_structured_answer(@_)) }
##
#############################################
## TEST CONTENTS
#############################################
##
## 1. GENERIC QUERIES
## 2. LANGUAGE BASED QUERIES
## 3. NO GO QUERIES
##
ddg_goodie_test(
[qw( DDG::Goodie::TextConverter )],
##
## 1. GENERIC QUERIES
## 
'binary converter' => test_zci(
'', structured_answer => build_structured_answer({
from_type => '',
to_type => 'binary'
})
),
'hex converter' => test_zci(
'', structured_answer => build_structured_answer({
from_type => '',
to_type => 'hexadecimal'
})
),
'ascii converter' => test_zci(
'', structured_answer => build_structured_answer({
from_type => '',
to_type => 'text'
})
),
'ansi conversion' => test_zci(
'', structured_answer => build_structured_answer({
from_type => '',
to_type => 'text'
})
),
'base64 encoder' => test_zci(
'', structured_answer => build_structured_answer({
from_type => '',
to_type => 'base64'
})
),
##
## 2. LANGUAGE BASED QUERIES
##
'hex to ascii' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'hexadecimal',
to_type => 'text'
})
),
'binary to text' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'binary',
to_type => 'text'
})
),
'binary from text' => test_zci( # should flip if the connecting word is 'from'
'', structured_answer => build_structured_answer({
from_type => 'text',
to_type => 'binary'
})
),
'text - hex' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'text',
to_type => 'hexadecimal'
})
),
'text - hex translation' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'text',
to_type => 'hexadecimal'
})
),
'binary to hex translator' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'binary',
to_type => 'hexadecimal'
})
),
'translate binary to decimal' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'binary',
to_type => 'decimal'
})
),
'convert rot13 to text' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'rot13',
to_type => 'text'
})
),
'convert base64 to ansi' => test_zci(
'', structured_answer => build_structured_answer({
from_type => 'base64',
to_type => 'text'
})
),
##
## 3. NO GO TRIGGERING
##
'bad example query' => undef,
'how to convert to binary javascript' => undef,
'binary calculator' => undef,
'what is ascii binary' => undef,
'how can I convert to rot13' => undef,
'what base is hexadecimal' => undef,
);
done_testing;