Skip to content

Commit

Permalink
feat: Add Morse cipher
Browse files Browse the repository at this point in the history
  • Loading branch information
manniL committed Mar 31, 2018
1 parent 9d014af commit 7123d02
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 0 deletions.
121 changes: 121 additions & 0 deletions docs/ciphers/morse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Morse

> Morse code transforms a message using short and long impulses. It was
one of the first telecommunication codes invented.

## Cipher behavior information

* Case sensitive? ❌
* Alphabet: `ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 .,:;?-_()'=+/@`
* Characters not in alphabet will be: **preserved**, **omitted** or **throwing an error (default)**

## Default options object

The options are the same for both methods, `encode` and `decode`

```
const options = {
separator: ' ', // Custom delimiter or glue
failOnUnknownCharacter: true, // Should an error be thrown when a character is not included in the alphabet
ommitUnknownCharacter: false // Should unknown character be ommitted or preserverd? (Only if failOnUnknownCharacter is false)
}
```

## Usage

### Encoding

#### Default

```
// Direct import if you need encode and decode
import { morse } from 'cipher-collection'
// morse.encode()/morse.decoode()
// Alternative
import { encode } from 'cipher-collection/morse'
console.log(encode('SOS')) // ... --- ...
```

#### With custom glue

```
import { encode } from 'cipher-collection/morse'
const options = { separator: '~' }
console.log(encode('SOS', options)) // ...~---~...
```

#### Without error throwing

There are **two options** when disabling errors:

```
import { encode } from 'cipher-collection/morse'
const preserveOptions = {
separator: '',
failOnUnknownCharacter: false,
ommitUnknownCharacter: false
}
// Preserve chraracters that can't get encoded
console.log(encode('€€€', preserveOptions)) // €€€
const ommitOptions = {
separator: '',
failOnUnknownCharacter: false,
ommitUnknownCharacter: true
}
// Ommit chraracters that can't get encoded
console.log(encode('€€€S', ommitOptions)) // ...
```


### Decoding

#### Default

```
import { decode } from 'cipher-collection/morse'
console.log(decode('... --- ...')) // SOS
```

#### With custom delimiter

```
import { decode } from 'cipher-collection/morse'
const options = { separator: '~' }
console.log(decode('...~---~...', options)) // SOS
```

#### Without error throwing

Similar to `encode`, there are **two options** when disabling errors:

```
import { decode } from 'cipher-collection/morse'
const preserveOptions = {
failOnUnknownCharacter: false,
ommitUnknownCharacter: false
}
// Preserve chraracters that can't get decoded
console.log(decode('.-.-.-.-.-', preserveOptions)) // .-.-.-.-.-
const ommitOptions = {
failOnUnknownCharacter: false,
ommitUnknownCharacter: true
}
// Ommit chraracters that can't get decoded
console.log(decode('.-.-.-.-.- ...', ommitOptions)) // S
```
94 changes: 94 additions & 0 deletions src/morse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
export const decode = (input, options = {}) => {
options = { ...DEFAULT_OPTIONS, ...options }
return input.split(options.separator).map(c => {
const decodedCharacter = Object.entries(ALPHABET).find(([k, v]) => v === c)

if (decodedCharacter) {
return decodedCharacter[0]
}
if (options.failOnUnknownCharacter) {
throw Error('Undecodable character')
}
return options.ommitUnknownCharacter ? '' : c
}).join('')
}

export const encode = (input, options = {}) => {
options = { ...DEFAULT_OPTIONS, ...options }

return [...input.toUpperCase()].map(c => {
const encodedCharacter = Object.entries(ALPHABET).find(([k]) => k === c)

if (encodedCharacter) {
return encodedCharacter[1]
}
if (options.failOnUnknownCharacter) {
throw Error('Unencodable character')
}
return options.ommitUnknownCharacter ? '' : c
}).join(options.separator)
}

const ALPHABET = {
A: '.-',
B: '-...',
C: '-.-.',
D: '-..',
E: '.',
F: '..-.',
G: '--.',
H: '....',
I: '..',
J: '.---',
K: '-.-',
L: '.-..',
M: '--',
N: '-.',
O: '---',
P: '.--.',
Q: '--.-',
R: '.-.',
S: '...',
T: '-',
U: '..-',
V: '...-',
W: '.--',
X: '-..-',
Y: '-.--',
Z: '--..',
1: '.----',
2: '..---',
3: '...--',
4: '....-',
5: '.....',
6: '-....',
7: '--...',
8: '---..',
9: '----.',
0: '-----',
' ': '/',
'.': '.-.-.-',
',': '--..--',
':': '---...',
';': '-.-.-.',
'?': '..--..',
'-': '-....-',
'_': '..--.-',
'(': '-.--.',
')': '-.--.-',
'\'': '.----.',
'=': '-...-',
'+': '.-.-.',
'/': '-..-.',
'@': '.--.-.'
}

const DEFAULT_OPTIONS = {
separator: ' ',
failOnUnknownCharacter: true,
ommitUnknownCharacter: false
}
export default {
decode,
encode
}
56 changes: 56 additions & 0 deletions test/morse.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import morse from 'morse'

const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 .,:;?-_()\'=+/@'
const encodedAlphabet = '.- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .--' +
' -..- -.-- --.. .---- ..--- ...-- ....- ..... -.... --... ---.. ----. ----- / .-.-.- --..-- ---... -.-.-.' +
' ..--.. -....- ..--.- -.--. -.--.- .----. -...- .-.-. -..-. .--.-.'
const invalidCharacter = {
input: '€S',
preserve: '€...',
ommit: '...'
}
const invalidMorseCharacter = {
input: '.-.-.-.-.-.- ...',
preserve: '.-.-.-.-.-.-S',
ommit: 'S'
}

describe('decoding', () => {
test('decoding the alphabet', () => {
expect(morse.decode(encodedAlphabet)).toBe(alphabet)
})

test('decoding with alternative delimiter', () => {
expect(morse.decode('..._---_...', {
separator: '_'
})).toBe('SOS')
})

test('decoding invalid character', () => {
expect(() => { morse.decode(invalidMorseCharacter.input) }).toThrowError('Undecodable character')
expect(morse.decode(invalidMorseCharacter.input, {
failOnUnknownCharacter: false
})).toBe(invalidMorseCharacter.preserve)
expect(morse.decode(invalidMorseCharacter.input, {
failOnUnknownCharacter: false,
ommitUnknownCharacter: true
})).toBe(invalidMorseCharacter.ommit)
})
})
describe('encoding', () => {
test('encoding the alphabet', () => {
expect(morse.encode(alphabet)).toBe(encodedAlphabet)
})
test('encoding invalid character', () => {
expect(() => { morse.encode(invalidCharacter.input) }).toThrowError('Unencodable character')
expect(morse.encode(invalidCharacter.input, {
separator: '',
failOnUnknownCharacter: false
})).toBe(invalidCharacter.preserve)
expect(morse.encode(invalidCharacter.input, {
separator: '',
failOnUnknownCharacter: false,
ommitUnknownCharacter: true
})).toBe(invalidCharacter.ommit)
})
})

0 comments on commit 7123d02

Please sign in to comment.