mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-08-28 13:50:49 +02:00
bug fixes, more cleanup, updated Docs/Readme
This commit is contained in:
@@ -134,6 +134,7 @@ Higher priority = more specific pattern (used for decoder result ordering):
|
||||
3. Test in webapp
|
||||
4. Add `detector` function if format has distinctive patterns
|
||||
5. Optionally add test cases to `tests/test_universal.js`
|
||||
6. Add a one-line description for the transform’s `name` in `DESCRIPTIONS` inside `build/readme-transform-section.js`, then run `node build/readme-transform-section.js` and merge the printed block into the **Text Transformations** section of the root `README.md` (the script exits with an error if a transform is missing from `DESCRIPTIONS`)
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -3,49 +3,66 @@ import BaseTransformer from '../BaseTransformer.js';
|
||||
|
||||
export default new BaseTransformer({
|
||||
|
||||
name: 'Alternating Case',
|
||||
priority: 150, // Higher priority to detect before Base64
|
||||
func: function(text) {
|
||||
let upper = true;
|
||||
return [...text].map(c => {
|
||||
if (/[a-zA-Z]/.test(c)) {
|
||||
const out = upper ? c.toUpperCase() : c.toLowerCase();
|
||||
upper = !upper;
|
||||
return out;
|
||||
name: 'Alternating Case',
|
||||
priority: 150, // Higher priority to detect before Base64
|
||||
startWith: 'upper', // 'upper' | 'lower' — first alphabetic letter (fallback when options omitted)
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'startWith',
|
||||
label: 'First alphabetic letter',
|
||||
type: 'select',
|
||||
default: 'upper',
|
||||
options: [
|
||||
{ value: 'upper', label: 'Uppercase' },
|
||||
{ value: 'lower', label: 'Lowercase' }
|
||||
]
|
||||
}
|
||||
],
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
const sw = options.startWith !== undefined && options.startWith !== ''
|
||||
? options.startWith
|
||||
: this.startWith;
|
||||
let upper = sw === 'lower' ? false : true;
|
||||
return [...text].map(c => {
|
||||
if (/[a-zA-Z]/.test(c)) {
|
||||
const out = upper ? c.toUpperCase() : c.toLowerCase();
|
||||
upper = !upper;
|
||||
return out;
|
||||
}
|
||||
return c;
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[alt case]';
|
||||
return this.func(text.slice(0, 6), options) + (text.length > 6 ? '...' : '');
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Reverse by lowercasing (loses original case pattern)
|
||||
return text.toLowerCase();
|
||||
},
|
||||
detector: function(text) {
|
||||
const cleaned = text.trim();
|
||||
if (cleaned.length < 4) return false;
|
||||
|
||||
// Check for alternating pattern in letters only
|
||||
let lastWasUpper = null;
|
||||
let alternations = 0;
|
||||
let letterCount = 0;
|
||||
|
||||
for (const char of cleaned) {
|
||||
if (/[a-zA-Z]/.test(char)) {
|
||||
const isUpper = char === char.toUpperCase();
|
||||
if (lastWasUpper !== null && isUpper !== lastWasUpper) {
|
||||
alternations++;
|
||||
}
|
||||
return c;
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[alt case]';
|
||||
return this.func(text.slice(0, 6)) + (text.length > 6 ? '...' : '');
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Reverse by lowercasing (loses original case pattern)
|
||||
return text.toLowerCase();
|
||||
},
|
||||
detector: function(text) {
|
||||
const cleaned = text.trim();
|
||||
if (cleaned.length < 4) return false;
|
||||
|
||||
// Check for alternating pattern in letters only
|
||||
let lastWasUpper = null;
|
||||
let alternations = 0;
|
||||
let letterCount = 0;
|
||||
|
||||
for (const char of cleaned) {
|
||||
if (/[a-zA-Z]/.test(char)) {
|
||||
const isUpper = char === char.toUpperCase();
|
||||
if (lastWasUpper !== null && isUpper !== lastWasUpper) {
|
||||
alternations++;
|
||||
}
|
||||
lastWasUpper = isUpper;
|
||||
letterCount++;
|
||||
}
|
||||
lastWasUpper = isUpper;
|
||||
letterCount++;
|
||||
}
|
||||
|
||||
// Must have at least 3 alternations and at least 70% alternation rate
|
||||
return letterCount >= 4 && alternations >= 3 && alternations >= letterCount * 0.7;
|
||||
}
|
||||
|
||||
// Must have at least 3 alternations and at least 70% alternation rate
|
||||
return letterCount >= 4 && alternations >= 3 && alternations >= letterCount * 0.7;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,39 +1,69 @@
|
||||
// bitwise NOT transform
|
||||
// Encode: UTF-8 bytes → NOT each byte → lossless lowercase hex (invalid UTF-8 after NOT is common).
|
||||
// Decode: hex → bytes → NOT each byte → UTF-8 decode (exact inverse of encode).
|
||||
// Helpers must live inside the default export: build-transforms.js concatenates the file body
|
||||
// into transforms[name] = … so multiple top-level declarations would assign the wrong value.
|
||||
import BaseTransformer from '../BaseTransformer.js';
|
||||
|
||||
export default new BaseTransformer({
|
||||
export default (function () {
|
||||
function utf8BytesBitwiseNot(bytes) {
|
||||
const out = new Uint8Array(bytes.length);
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
out[i] = ~bytes[i] & 0xFF;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function bytesToHex(bytes) {
|
||||
return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
function hexToBytes(hex) {
|
||||
const cleaned = hex.replace(/\s+/g, '').replace(/0x/gi, '');
|
||||
if (cleaned.length % 2 !== 0) {
|
||||
return null;
|
||||
}
|
||||
if (!/^[0-9a-fA-F]*$/.test(cleaned)) {
|
||||
return null;
|
||||
}
|
||||
const out = new Uint8Array(cleaned.length / 2);
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
out[i] = parseInt(cleaned.slice(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
return new BaseTransformer({
|
||||
name: 'Bitwise NOT',
|
||||
priority: 100,
|
||||
category: 'format',
|
||||
func: function(text) {
|
||||
// Invert all bits in each byte
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
const result = new Uint8Array(bytes.length);
|
||||
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
result[i] = ~bytes[i] & 0xFF; // NOT operation, mask to 8 bits
|
||||
}
|
||||
|
||||
try {
|
||||
return new TextDecoder().decode(result);
|
||||
} catch (e) {
|
||||
// If decoding fails, return as hex
|
||||
return Array.from(result).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
const inverted = utf8BytesBitwiseNot(bytes);
|
||||
return bytesToHex(inverted);
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Bitwise NOT is self-reciprocal (NOT NOT = original)
|
||||
return this.func(text);
|
||||
const inverted = hexToBytes(text);
|
||||
if (!inverted) {
|
||||
return '[invalid hex - paste the hex from encode; spaces allowed]';
|
||||
}
|
||||
const bytes = utf8BytesBitwiseNot(inverted);
|
||||
try {
|
||||
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
||||
} catch (e) {
|
||||
return '[invalid UTF-8 after inverse NOT]';
|
||||
}
|
||||
},
|
||||
preview: function(text) {
|
||||
if (!text) return '[bitwise-not]';
|
||||
return this.func(text.slice(0, 5));
|
||||
const h = this.func(text.slice(0, 5));
|
||||
return h.length > 24 ? `${h.slice(0, 24)}…` : h;
|
||||
},
|
||||
detector: function(text) {
|
||||
// Bitwise NOT produces scrambled text, hard to detect
|
||||
// Check for non-printable characters or unusual patterns
|
||||
const hasNonPrintable = /[\x00-\x1F\x7F-\x9F]/.test(text);
|
||||
return hasNonPrintable && text.length >= 5;
|
||||
const t = text.trim().replace(/\s+/g, '');
|
||||
if (t.length < 8 || t.length % 2 !== 0) return false;
|
||||
return /^[0-9a-fA-F]+$/.test(t);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
@@ -5,10 +5,27 @@ export default new BaseTransformer({
|
||||
name: 'Indent',
|
||||
priority: 100,
|
||||
category: 'format',
|
||||
spaces: 4, // Default indent spaces
|
||||
func: function(text) {
|
||||
const spaces = parseInt(this.spaces) || 4;
|
||||
const indent = ' '.repeat(spaces);
|
||||
spaces: 4, // Default indent spaces (fallback when options omitted)
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'spaces',
|
||||
label: 'Spaces per indent',
|
||||
type: 'number',
|
||||
default: 4,
|
||||
min: 1,
|
||||
max: 32,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
let s = options.spaces !== undefined && options.spaces !== ''
|
||||
? parseInt(options.spaces, 10)
|
||||
: parseInt(this.spaces, 10) || 4;
|
||||
if (Number.isNaN(s) || s < 1) {
|
||||
s = 4;
|
||||
}
|
||||
const indent = ' '.repeat(s);
|
||||
|
||||
return text.split('\n').map(line => indent + line).join('\n');
|
||||
},
|
||||
@@ -16,9 +33,9 @@ export default new BaseTransformer({
|
||||
// Remove leading spaces from each line
|
||||
return text.split('\n').map(line => line.replace(/^\s+/, '')).join('\n');
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[indent]';
|
||||
return this.func(text.slice(0, 20));
|
||||
return this.func(text.slice(0, 20), options);
|
||||
},
|
||||
detector: function(text) {
|
||||
// Check if all lines start with same amount of whitespace
|
||||
|
||||
@@ -5,15 +5,48 @@ export default new BaseTransformer({
|
||||
name: 'Line Numbers',
|
||||
priority: 100,
|
||||
category: 'format',
|
||||
start: 1, // Starting line number
|
||||
func: function(text) {
|
||||
const start = parseInt(this.start) || 1;
|
||||
start: 1, // Starting line number (fallback when options omitted)
|
||||
gutterWidth: 4, // padStart width for the number column
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'start',
|
||||
label: 'Starting line number',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
min: 0,
|
||||
max: 9999999,
|
||||
step: 1
|
||||
},
|
||||
{
|
||||
id: 'gutterWidth',
|
||||
label: 'Number column width',
|
||||
type: 'number',
|
||||
default: 4,
|
||||
min: 1,
|
||||
max: 12,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
let start = options.start !== undefined && options.start !== ''
|
||||
? parseInt(options.start, 10)
|
||||
: parseInt(this.start, 10) || 1;
|
||||
if (Number.isNaN(start)) {
|
||||
start = 1;
|
||||
}
|
||||
let gutterWidth = options.gutterWidth !== undefined && options.gutterWidth !== ''
|
||||
? parseInt(options.gutterWidth, 10)
|
||||
: parseInt(this.gutterWidth, 10) || 4;
|
||||
if (Number.isNaN(gutterWidth) || gutterWidth < 1) {
|
||||
gutterWidth = 4;
|
||||
}
|
||||
const lines = text.split('\n');
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const lineNum = start + i;
|
||||
result += lineNum.toString().padStart(4, ' ') + ': ' + lines[i] + '\n';
|
||||
result += lineNum.toString().padStart(gutterWidth, ' ') + ': ' + lines[i] + '\n';
|
||||
}
|
||||
|
||||
return result.trimEnd();
|
||||
@@ -24,9 +57,9 @@ export default new BaseTransformer({
|
||||
return line.replace(/^\s*\d+\s*:\s*/, '');
|
||||
}).join('\n');
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[line-numbers]';
|
||||
return this.func(text.slice(0, 30));
|
||||
return this.func(text.slice(0, 30), options);
|
||||
},
|
||||
detector: function(text) {
|
||||
// Check for line number pattern at start of lines
|
||||
|
||||
@@ -5,11 +5,42 @@ export default new BaseTransformer({
|
||||
name: 'Text Justify',
|
||||
priority: 100,
|
||||
category: 'format',
|
||||
width: 80, // Default width
|
||||
width: 80, // Default width (fallback when options omitted)
|
||||
align: 'left', // left, right, center
|
||||
func: function(text) {
|
||||
const width = parseInt(this.width) || 80;
|
||||
const align = this.align || 'left';
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'width',
|
||||
label: 'Line width (characters)',
|
||||
type: 'number',
|
||||
default: 80,
|
||||
min: 8,
|
||||
max: 200,
|
||||
step: 1
|
||||
},
|
||||
{
|
||||
id: 'align',
|
||||
label: 'Alignment',
|
||||
type: 'select',
|
||||
default: 'left',
|
||||
options: [
|
||||
{ value: 'left', label: 'Left (pad on the right)' },
|
||||
{ value: 'right', label: 'Right (pad on the left)' },
|
||||
{ value: 'center', label: 'Center' }
|
||||
]
|
||||
}
|
||||
],
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
let w = options.width !== undefined && options.width !== ''
|
||||
? parseInt(options.width, 10)
|
||||
: parseInt(this.width, 10) || 80;
|
||||
if (Number.isNaN(w) || w < 1) {
|
||||
w = 80;
|
||||
}
|
||||
const width = w;
|
||||
const align = options.align !== undefined && options.align !== ''
|
||||
? options.align
|
||||
: (this.align || 'left');
|
||||
|
||||
const lines = text.split('\n');
|
||||
let result = '';
|
||||
@@ -47,9 +78,9 @@ export default new BaseTransformer({
|
||||
// Remove padding spaces
|
||||
return text.split('\n').map(line => line.trim()).join('\n');
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[text-justify]';
|
||||
return this.func(text.slice(0, 20));
|
||||
return this.func(text.slice(0, 20), options);
|
||||
},
|
||||
detector: function(text) {
|
||||
// Check for consistent line lengths with padding
|
||||
|
||||
@@ -5,11 +5,28 @@ export default new BaseTransformer({
|
||||
name: 'Word Wrap',
|
||||
priority: 100,
|
||||
category: 'format',
|
||||
width: 80, // Default wrap width
|
||||
func: function(text) {
|
||||
const width = parseInt(this.width) || 80;
|
||||
if (width < 1) return text;
|
||||
|
||||
width: 80, // Default wrap width (fallback when options omitted)
|
||||
configurableOptions: [
|
||||
{
|
||||
id: 'width',
|
||||
label: 'Maximum line width (characters)',
|
||||
type: 'number',
|
||||
default: 80,
|
||||
min: 8,
|
||||
max: 200,
|
||||
step: 1
|
||||
}
|
||||
],
|
||||
func: function(text, options) {
|
||||
options = options || {};
|
||||
let w = options.width !== undefined && options.width !== ''
|
||||
? parseInt(options.width, 10)
|
||||
: parseInt(this.width, 10) || 80;
|
||||
if (Number.isNaN(w) || w < 1) {
|
||||
w = 80;
|
||||
}
|
||||
const width = w;
|
||||
|
||||
const lines = text.split('\n');
|
||||
let result = '';
|
||||
|
||||
@@ -45,9 +62,9 @@ export default new BaseTransformer({
|
||||
// Remove line breaks (simple approach - may not be perfect)
|
||||
return text.replace(/\n/g, ' ');
|
||||
},
|
||||
preview: function(text) {
|
||||
preview: function(text, options) {
|
||||
if (!text) return '[word-wrap]';
|
||||
return this.func(text.slice(0, 50));
|
||||
return this.func(text.slice(0, 50), options);
|
||||
},
|
||||
detector: function(text) {
|
||||
// Check if text has consistent line lengths
|
||||
|
||||
Reference in New Issue
Block a user