sub_cipher 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: efc3e2913656d45254d35d24732ffe43218d23e8
4
+ data.tar.gz: 0425ba813eb72cf118b906863b7ee445564c3c5d
5
+ SHA512:
6
+ metadata.gz: 2ed311f95e325bd5c6253c16370e4d4a8bcd7d99fd6accb6412b052a2095973542fe88e5ec04d1ac8cdc8790deb531f930c173c8f7b9332d2f01490109d154af
7
+ data.tar.gz: f626cd93638eb663944c9b48ddb0ac7f10fcc2fe43be608bdf51326d78047d45f676cf8d22477925cbc80bf93019d659fd1b2716188c60c78497f1a8a9397353
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/HISTORY.md ADDED
@@ -0,0 +1,7 @@
1
+ ## v1.0.1 / 2014-10-15
2
+
3
+ * [Doc] Fix too long description issue in gemspec
4
+
5
+ ## v1.0.0 / 2014-10-15
6
+
7
+ * [Init] Initial Release
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Sibevin Wang
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Sub(stitution) Cipher
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/sub_cipher.png)][gem]
4
+ [![Build Status](https://travis-ci.org/sibevin/sub_cipher.png?branch=build)][travis]
5
+ [![Coverage Status](https://coveralls.io/repos/sibevin/sub_cipher/badge.png?branch=cover-check)][coveralls]
6
+
7
+ [gem]: https://rubygems.org/gems/sub_cipher
8
+ [travis]: https://travis-ci.org/sibevin/sub_cipher
9
+ [coveralls]:https://coveralls.io/r/sibevin/sub_cipher?branch=cover-check
10
+
11
+ Encode/Decode text with the substitution cipher
12
+
13
+ ## Installation
14
+
15
+ $ gem install sub_cipher
16
+
17
+ ## Usage
18
+
19
+ Use default seeds(alphabets) to generate the mapping, note that the letters with different cases would be mapped to the same letter, for example: if "a" is mapped to "b", then "A" is mapped to "B".
20
+
21
+ sc = SubCipher.gen
22
+ sc.encode("Here is a secret.")
23
+ # "Wyky gn q nyakyr."
24
+ sc.decode("Wyky gn q nyakyr.")
25
+ # "Here is a secret."
26
+ sc.seed
27
+ # "abcdefghijklmnopqrstuvwxyz"
28
+ sc.map
29
+ # "qeahyftwgpjixodzbknrlscvum"
30
+
31
+ The `seed` and `map` method shows how to map the cipher, the above example means mapping `"abcdefghijklmnopqrstuvwxyz"` to `"qeahyftwgpjixodzbknrlscvum"`
32
+
33
+ ## Options
34
+
35
+ ### Seed
36
+
37
+ Use `:s` (or `:seed`) to map the given seeds only
38
+
39
+ sc = SubCipher.gen(seed: "abcde")
40
+ sc.encode("Here is a secret.")
41
+ # "Hcrc is e scbrct."
42
+ sc.decode("Hcrc is e scbrct.")
43
+ # "Here is a secret."
44
+ sc.seed
45
+ # "abcde"
46
+ sc.map
47
+ # "edbac"
48
+
49
+ ### Map
50
+
51
+ Use `:m` (or `:map`) option to initalize cipher with a map. Note that the `:seed` option would be skipped if both `:seed` and `:map` options are given.
52
+
53
+ sc = SubCipher.gen(seed: "bdeac")
54
+ sc.encode("Here is a secret.")
55
+ # "Hara is b saerat."
56
+ sc.decode("Hara is b saerat.")
57
+ # "Here is a secret."
58
+ sc.seed
59
+ # "abcde"
60
+ sc.map
61
+ # "bdeca"
62
+
63
+ ### Keep Case
64
+
65
+ If you want to map letters with different cases to different letters, use `k: false` (or `keep_case: false`) option.
66
+
67
+ sc = SubCipher.gen(keep_case: false)
68
+ sc.encode("Here is a secret.")
69
+ # "alXl wE s EleXlk."
70
+ sc.decode("alXl wE s EleXlk.")
71
+ # "Here is a secret."
72
+ sc.seed
73
+ # "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
74
+ sc.map
75
+ # "PNAgWKOaxtCUdqHpIuJRhjTMnDsbeQlFiGwrzLfBvVYXEkZcoSmy"
76
+
77
+ If there is a seed(assigned by `seed` or `map` option) which is not an alphabet, `keep_case: false` would be applied.
78
+
79
+ sc = SubCipher.gen(seed: "abcdeABCDE ,.")
80
+ sc.encode("Here is a secret.")
81
+ # "HdrdAisA,AsdBrdtE"
82
+ sc.decode("HdrdAisA,AsdBrdtE")
83
+ # "Here is a secret."
84
+ sc.seed
85
+ # " ,.ABCDEabcde"
86
+ sc.map
87
+ # "AaE ebCc,DB.d"
88
+
89
+ ## Test
90
+
91
+ Go to gem folder and run
92
+
93
+ ruby ./test/test_all.rb
94
+
95
+ (Note that you need minitest ~> 5.0 to run these tests)
96
+
97
+ # Contributing
98
+
99
+ 1. Fork it ( https://github.com/sibevin/sub_cipher/fork )
100
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
101
+ 3. Write tests for your code
102
+ 4. Commit your changes (both code and tests) (`git commit -am 'Add some feature'`)
103
+ 5. Push to the branch (`git push origin my-new-feature`)
104
+ 6. Create a new Pull Request
105
+
106
+ ## Authors
107
+
108
+ Sibevin Wang
109
+
110
+ ## Copyright
111
+
112
+ Copyright (c) 2014 Sibevin Wang. Released under the MIT license.
data/lib/sub_cipher.rb ADDED
@@ -0,0 +1,161 @@
1
+ require "sub_cipher/sub_cipher_error"
2
+
3
+ module SubCipher
4
+ # The alphabet seed
5
+ ALPHABETS = ('a'..'z')
6
+
7
+ # The supported options
8
+ SUPPORTED_OPTS = {
9
+ :seed => {
10
+ :abbr => :s,
11
+ :type => String
12
+ },
13
+ :map => {
14
+ :abbr => :m,
15
+ :type => String
16
+ },
17
+ :keep_case => {
18
+ :abbr => :k
19
+ },
20
+ }
21
+
22
+ # The default options
23
+ DEFAULT_OPTS = {
24
+ :seed => [ALPHABETS.to_a, ALPHABETS.to_a.map{ |a| a.upcase }].flatten.join,
25
+ :keep_case => true
26
+ }
27
+
28
+ # The major method to generate a sub cipher.
29
+ # @param options [Hash]
30
+ # The options to tell sub cipher how to encode/decode.
31
+ # Three options :seed, :map and :keep_case are supported.
32
+ # Please see {file:README} to get more examples
33
+ # @return [SubCipherObject]
34
+ # The generated sub cipher.
35
+ # @raise [SubCipherError]
36
+ # Please see {SubCipher::SubCipherError}
37
+ def SubCipher.gen(options = {})
38
+ SubCipherObject.new(options)
39
+ end
40
+
41
+ # The sub cipher class.
42
+ class SubCipherObject
43
+ # The sub cipher contructor.
44
+ # @param options (see SubCipher.gen)
45
+ # @return (see SubCipher.gen)
46
+ # @raise (see SubCipher.gen)
47
+ def initialize(options)
48
+ opts = check_opt(options)
49
+ opts.each do |key, value|
50
+ if (!SUPPORTED_OPTS.key?(key) && !SUPPORTED_OPTS.values.map {|v| v[:abbr]}.include?(key))
51
+ raise SubCipherError.new(:unknown_option, key)
52
+ end
53
+ case key
54
+ when :seed
55
+ @map = value.chars.to_a.uniq.shuffle
56
+ when :map
57
+ @map = value.chars.to_a.uniq
58
+ when :keep_case
59
+ @keep_case = (value ? true : false)
60
+ end
61
+ end
62
+ support_keep_case = true
63
+ @map.each do |m|
64
+ if !ALPHABETS.include?(m.downcase)
65
+ support_keep_case = false
66
+ break
67
+ end
68
+ end
69
+ @keep_case = (support_keep_case && @keep_case ? true : false)
70
+ if @keep_case
71
+ @map = @map.map{ |m| m.downcase }.uniq
72
+ end
73
+ seed = @map.sort
74
+ @mapping = {}
75
+ seed.each_with_index do |s, index|
76
+ @mapping[s] = @map[index]
77
+ end
78
+ end
79
+
80
+ # Encode the given string.
81
+ # @param str [String]
82
+ # The original string to encode.
83
+ # @return [String]
84
+ # The encoded result.
85
+ def encode(str)
86
+ str.chars.map { |c| convert(c) }.join
87
+ end
88
+
89
+ # Decode the given string.
90
+ # @param str [String]
91
+ # The encoded string to decode.
92
+ # @return [String]
93
+ # The decoded result.
94
+ def decode(str)
95
+ str.chars.map { |c| convert(c, true) }.join
96
+ end
97
+
98
+ # Show the map string, for mapping display.
99
+ # For example, if the seed string is "abc" and map string is "cab",
100
+ # then the mapping is "a" => "c", "b" => "a", "c" => "b".
101
+ # @return [String]
102
+ # The map string.
103
+ def map
104
+ @map.join
105
+ end
106
+
107
+ # Show the seed string, for mapping display.
108
+ # For example, if the seed string is "abc" and map string is "cab",
109
+ # then the mapping is "a" => "c", "b" => "a", "c" => "b".
110
+ # @return [String]
111
+ # The seed string.
112
+ def seed
113
+ @map.sort.join
114
+ end
115
+
116
+ private
117
+
118
+ # Covert single char with the mapping.
119
+ def convert(char, reverse = false)
120
+ if @keep_case
121
+ if reverse
122
+ if @mapping.value?(char)
123
+ return @mapping.index(char)
124
+ elsif @mapping.value?(char.downcase)
125
+ return @mapping.index(char.downcase).upcase
126
+ else
127
+ return char
128
+ end
129
+ else
130
+ if @mapping.key?(char)
131
+ return @mapping[char]
132
+ elsif @mapping.key?(char.downcase)
133
+ return @mapping[char.downcase].upcase
134
+ else
135
+ return char
136
+ end
137
+ end
138
+ else
139
+ if reverse
140
+ @mapping.value?(char) ? @mapping.index(char) : char
141
+ else
142
+ @mapping.key?(char) ? @mapping[char] : char
143
+ end
144
+ end
145
+ end
146
+
147
+ # Check the given options.
148
+ def check_opt(opts)
149
+ SUPPORTED_OPTS.each do |key, value|
150
+ if (opts[key] != nil) && opts.keys.include?(value[:abbr])
151
+ raise SubCipherError.new(:duplicated_option, opts)
152
+ end
153
+ opts.merge!(key => opts[value[:abbr]]) if opts[value[:abbr]] != nil
154
+ if (opts[key] != nil) && (value[:type] != nil) && !(opts[key]).is_a?(value[:type])
155
+ raise SubCipherError.new(:invalid_option_value, opts)
156
+ end
157
+ end
158
+ DEFAULT_OPTS.merge(opts)
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,46 @@
1
+ module SubCipher
2
+
3
+ # A customized exception for SubCipher
4
+ class SubCipherError < StandardError
5
+
6
+ # Errors used in SubCipher
7
+ ERRORS = {
8
+ :unknown_option => {
9
+ :value => 1,
10
+ :msg => "Unknown option"
11
+ },
12
+ :duplicated_option => {
13
+ :value => 2,
14
+ :msg => "The same options are given."
15
+ },
16
+ :invalid_option_value => {
17
+ :value => 3,
18
+ :msg => "The given option value is invalid."
19
+ },
20
+ }
21
+
22
+ attr_reader :code, :value, :msg, :info
23
+
24
+ # The SubCipherError constructor.
25
+ # @param error [Fixnum, String]
26
+ # You can give a error number defined in the keys of {SubCipher::SubCipherError::ERRORS} or a string message for internal usage.
27
+ # @param info [Hash]
28
+ # Anything you want to put in the info attribute of SubCipherError.
29
+ def initialize(error, info = {})
30
+ @code = error
31
+ @info = info
32
+ if ERRORS.keys.include?(error)
33
+ @value = ERRORS[error][:value]
34
+ @msg = ERRORS[error][:msg]
35
+ elsif error.class.name == 'String'
36
+ @code = :internal
37
+ @value = 90000
38
+ @msg = error
39
+ else
40
+ @code = :internal
41
+ @value = 99999
42
+ @msg = "Internal Error"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module SubCipher
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sub_cipher/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sub_cipher"
8
+ spec.version = SubCipher::VERSION
9
+ spec.authors = ["Sibevin Wang"]
10
+ spec.email = ["sibevin@gmail.com"]
11
+ spec.summary = %q{Encode/Decode text with substitution cipher}
12
+ spec.description = %q{Encode/Decode text with substitution cipher, please see "README" to get more details.}
13
+ spec.homepage = "https://github.com/sibevin/sub_cipher"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency 'minitest', '~> 5.0'
22
+ end
data/test/test_all.rb ADDED
@@ -0,0 +1,97 @@
1
+ require 'minitest/autorun'
2
+
3
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}")
4
+ require "test_helper"
5
+ require "sub_cipher"
6
+
7
+ class TestByte < Minitest::Test
8
+ def test_should_encode_and_decode_with_default_seed
9
+ sc = SubCipher.gen()
10
+ target_str = 'In cryptography, a substitution cipher is a method of encoding by which units of plaintext are replaced with ciphertext, according to a regular system; the "units" may be single letters (the most common), pairs of letters, triplets of letters, mixtures of the above, and so forth. The receiver deciphers the text by performing an inverse substitution.'
11
+ encoded_str = sc.encode(target_str)
12
+ decoded_str = sc.decode(encoded_str)
13
+ assert_equal(decoded_str, target_str)
14
+ assert_equal(sc.seed, ('a'..'z').to_a.join)
15
+ assert_match(/^[a-z]*$/, sc.map)
16
+ end
17
+
18
+ def test_should_encode_and_decode_with_keep_case_false
19
+ sc = SubCipher.gen(:keep_case => false)
20
+ target_str = 'In cryptography, a substitution cipher is a method of encoding by which units of plaintext are replaced with ciphertext, according to a regular system; the "units" may be single letters (the most common), pairs of letters, triplets of letters, mixtures of the above, and so forth. The receiver deciphers the text by performing an inverse substitution.'
21
+ encoded_str = sc.encode(target_str)
22
+ decoded_str = sc.decode(encoded_str)
23
+ assert_equal(decoded_str, target_str)
24
+ assert_equal(sc.seed, [('a'..'z').to_a, ('a'..'z').to_a.map{ |a| a.upcase }].flatten.sort.join)
25
+ assert_match(/^[a-zA-Z]*$/, sc.map)
26
+
27
+ sc = SubCipher.gen(:k => false)
28
+ target_str = 'In cryptography, a substitution cipher is a method of encoding by which units of plaintext are replaced with ciphertext, according to a regular system; the "units" may be single letters (the most common), pairs of letters, triplets of letters, mixtures of the above, and so forth. The receiver deciphers the text by performing an inverse substitution.'
29
+ encoded_str = sc.encode(target_str)
30
+ decoded_str = sc.decode(encoded_str)
31
+ assert_equal(decoded_str, target_str)
32
+ end
33
+
34
+ def test_should_encode_the_given_seed_only
35
+ seed = "abc"
36
+ sc = SubCipher.gen(:seed => seed)
37
+ target_str = "aaabbbcccdddeee"
38
+ encoded_str = sc.encode(target_str)
39
+ assert_match(/^[abc]{9}dddeee$/, encoded_str)
40
+ assert_equal(sc.seed, seed.chars.sort.join)
41
+ assert_match(/^[abc]*$/, sc.map)
42
+
43
+ sc = SubCipher.gen(:s => seed)
44
+ target_str = "aaabbbcccdddeee"
45
+ encoded_str = sc.encode(target_str)
46
+ assert_match(/^[abc]{9}dddeee$/, encoded_str)
47
+ end
48
+
49
+ def test_should_encode_with_the_given_map
50
+ map = "bca"
51
+ sc = SubCipher.gen(:map => map)
52
+ target_str = "aaabbbcccdddeee"
53
+ encoded_str = sc.encode(target_str)
54
+ assert(encoded_str == "bbbcccaaadddeee")
55
+ assert_equal(sc.seed, map.chars.to_a.uniq.sort.join)
56
+ assert_match(/^[abc]*$/, sc.map)
57
+
58
+ sc = SubCipher.gen(:m => map)
59
+ target_str = "aaabbbcccdddeee"
60
+ encoded_str = sc.encode(target_str)
61
+ assert(encoded_str == "bbbcccaaadddeee")
62
+ end
63
+
64
+ def test_should_keep_the_case_by_default
65
+ map = "bca"
66
+ sc = SubCipher.gen(:map => map)
67
+ target_str = "AAAbbbcccdddeee"
68
+ encoded_str = sc.encode(target_str)
69
+ assert(encoded_str == "BBBcccaaadddeee")
70
+ assert_equal(sc.seed, map.chars.to_a.uniq.sort.join)
71
+ assert_match(/^[abc]*$/, sc.map)
72
+ end
73
+
74
+ def test_should_mix_cases_if_keep_case_false
75
+ seed = "abcABC"
76
+ sc = SubCipher.gen(:seed => seed, :keep_case => false)
77
+ target_str = "aaabbbcccdddeee"
78
+ encoded_str = sc.encode(target_str)
79
+ assert_match(/^[abcABC]{9}dddeee$/, encoded_str)
80
+ assert_equal(sc.seed, seed.chars.sort.join)
81
+ end
82
+
83
+ def test_should_raise_unknown_option_exception
84
+ e = assert_raises(SubCipher::SubCipherError) { SubCipher.gen(:unknown_option => "unknown_option") }
85
+ assert(e.code == :unknown_option)
86
+ end
87
+
88
+ def test_should_raise_duplicated_option_exception
89
+ e = assert_raises(SubCipher::SubCipherError) { SubCipher.gen(:map => "abc", :m => "def") }
90
+ assert(e.code == :duplicated_option)
91
+ end
92
+
93
+ def test_should_raise_invalid_option_value_exception
94
+ e = assert_raises(SubCipher::SubCipherError) { SubCipher.gen(:map => ["a","b","c"]) }
95
+ assert(e.code == :invalid_option_value)
96
+ end
97
+ end
@@ -0,0 +1 @@
1
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib")
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sub_cipher
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sibevin Wang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ description: Encode/Decode text with substitution cipher, please see "README" to get
28
+ more details.
29
+ email:
30
+ - sibevin@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - HISTORY.md
37
+ - LICENSE.txt
38
+ - README.md
39
+ - lib/sub_cipher.rb
40
+ - lib/sub_cipher/sub_cipher_error.rb
41
+ - lib/sub_cipher/version.rb
42
+ - sub_cipher.gemspec
43
+ - test/test_all.rb
44
+ - test/test_helper.rb
45
+ homepage: https://github.com/sibevin/sub_cipher
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.2.2
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Encode/Decode text with substitution cipher
69
+ test_files:
70
+ - test/test_all.rb
71
+ - test/test_helper.rb