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 stylesmaster
parent
b9297e004a
commit
588c354075
|
@ -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;
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -0,0 +1,9 @@
|
|||
text:
|
||||
- ascii
|
||||
- ansi
|
||||
binary:
|
||||
decimal:
|
||||
hexadecimal:
|
||||
- hex
|
||||
base64:
|
||||
rot13:
|
|
@ -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;
|
Loading…
Reference in New Issue