smstools 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +11 -0
- data/lib/assets/javascripts/sms_tools/index.js.coffee +1 -0
- data/lib/assets/javascripts/sms_tools/message.js.coffee +58 -0
- data/lib/sms_tools.rb +7 -0
- data/lib/sms_tools/encoding_detection.rb +62 -0
- data/lib/sms_tools/gsm_encoding.rb +195 -0
- data/lib/sms_tools/rails/engine.rb +6 -0
- data/lib/sms_tools/version.rb +3 -0
- data/lib/smstools.rb +1 -0
- data/smstools.gemspec +25 -0
- data/spec/sms_tools/encoding_detection_spec.rb +153 -0
- data/spec/spec_helper.rb +8 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4180ff46eb40f9d709cf9895d1dc493aeec4203c
|
4
|
+
data.tar.gz: cba4fd705516f2418dfae93ce3732231e30faba5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b3b26b73c00f2acbb9e107ecc24738b97a713a3579c685339226b9ba369c0d2080ff7b40357a472c0dc9ebe41bca0f8ed38e28cd132242ce7ed3e514a78d69c3
|
7
|
+
data.tar.gz: 9b7a0e6a64fdaec4f8f05130dc3b4ac735f2848fddfd1bbd8d274ec2b6e9781fcdb993ef121357e77f6b4c486508a067e6480516a72fd33d7c28007397f0fde5
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dimitar Dimitrov
|
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,71 @@
|
|
1
|
+
# Sms Tools
|
2
|
+
|
3
|
+
A small collection of useful Ruby and JavaScript classes implementing often
|
4
|
+
needed functionality for dealing with SMS messages.
|
5
|
+
|
6
|
+
The gem is also a Rails engine and using it in your Rails app will allow you
|
7
|
+
to also use the JavaScript classes via the asset pipeline.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
The following features are available on both the server side and the client
|
12
|
+
side:
|
13
|
+
|
14
|
+
- Detection of the most optimal encoding for sending an SMS message (GSM 7-bit
|
15
|
+
or Unicode).
|
16
|
+
- Correctly determining the message's length according to the most optimal
|
17
|
+
encoding.
|
18
|
+
- Concatenation detection and concatenated message parts counting.
|
19
|
+
|
20
|
+
And more.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
gem 'smstools'
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install smstools
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
The gem consists of both server-side (Ruby) and client-side classes. You can
|
39
|
+
use either one.
|
40
|
+
|
41
|
+
### Server-side code
|
42
|
+
|
43
|
+
If you use the gem in Rails or via Bundler, just use the appropriate class,
|
44
|
+
such as `SmsTools::EncodingDetection` or `SmsTools::GsmEncoding`.
|
45
|
+
|
46
|
+
#### `EncodingDetection`
|
47
|
+
#### `GsmEncoding`
|
48
|
+
|
49
|
+
### Client-side code
|
50
|
+
|
51
|
+
If you're using the gem in Rails 3.x or newer, you can just add the following
|
52
|
+
to your `application.js` file to gain access to the JavaScript classes:
|
53
|
+
|
54
|
+
#= require 'sms_tools/all'
|
55
|
+
|
56
|
+
Or require only the files you need:
|
57
|
+
|
58
|
+
#= require 'sms_tools/message'
|
59
|
+
|
60
|
+
Note that this assumes you're using the asset pipeline. You need to have a
|
61
|
+
CoffeeScript preprocessor set up.
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
|
65
|
+
1. [Fork the repo](http://github.com/mitio/smstools/fork)
|
66
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
67
|
+
3. Make your changes and provide tests for them
|
68
|
+
4. Make sure all tests pass (run them with `bundle exec rake test`)
|
69
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
70
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
7. Send a pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
#= require_tree .
|
@@ -0,0 +1,58 @@
|
|
1
|
+
window.SmsTools ?= {}
|
2
|
+
|
3
|
+
class SmsTools.Message
|
4
|
+
maxLengthForEncoding:
|
5
|
+
gsm:
|
6
|
+
normal: 160
|
7
|
+
concatenated: 153
|
8
|
+
unicode:
|
9
|
+
normal: 70
|
10
|
+
concatenated: 67
|
11
|
+
|
12
|
+
doubleByteCharsInGsmEncoding:
|
13
|
+
'^': true
|
14
|
+
'{': true
|
15
|
+
'}': true
|
16
|
+
'[': true
|
17
|
+
'~': true
|
18
|
+
']': true
|
19
|
+
'|': true
|
20
|
+
'€': true
|
21
|
+
'\\': true
|
22
|
+
|
23
|
+
gsmEncodingPattern: /^[0-9a-zA-Z@Δ¡¿£_!Φ"¥Γ#èΛ¤éΩ%ùΠ&ìΨòΣçΘΞ:Ø;ÄäøÆ,<Ööæ=ÑñÅß>Üüåɧà€~ \$\.\-\+\(\)\*\\\/\?\|\^\}\{\[\]\'\r\n]*$/
|
24
|
+
|
25
|
+
constructor: (@text) ->
|
26
|
+
@text = @text.replace /\r\n?/g, "\n"
|
27
|
+
@encoding = @_encoding()
|
28
|
+
@length = @_length()
|
29
|
+
@concatenatedPartsCount = @_concatenatedPartsCount()
|
30
|
+
|
31
|
+
maxLengthFor: (concatenatedPartsCount) ->
|
32
|
+
messageType = if concatenatedPartsCount > 1 then 'concatenated' else 'normal'
|
33
|
+
|
34
|
+
concatenatedPartsCount * @maxLengthForEncoding[@encoding][messageType]
|
35
|
+
|
36
|
+
_encoding: ->
|
37
|
+
if @gsmEncodingPattern.test(@text) then 'gsm' else 'unicode'
|
38
|
+
|
39
|
+
_concatenatedPartsCount: ->
|
40
|
+
encoding = @encoding
|
41
|
+
length = @length
|
42
|
+
|
43
|
+
if length <= @maxLengthForEncoding[encoding].normal
|
44
|
+
1
|
45
|
+
else
|
46
|
+
parseInt Math.ceil(length / @maxLengthForEncoding[encoding].concatenated), 10
|
47
|
+
|
48
|
+
# Returns the number of symbols, which the given text will take up in an SMS
|
49
|
+
# message, taking into account any double-space symbols in the GSM 03.38
|
50
|
+
# encoding.
|
51
|
+
_length: ->
|
52
|
+
length = @text.length
|
53
|
+
|
54
|
+
if @encoding == 'gsm'
|
55
|
+
for char in @text
|
56
|
+
length += 1 if @doubleByteCharsInGsmEncoding[char]
|
57
|
+
|
58
|
+
length
|
data/lib/sms_tools.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'sms_tools/gsm_encoding'
|
2
|
+
|
3
|
+
module SmsTools
|
4
|
+
class EncodingDetection
|
5
|
+
MAX_LENGTH_FOR_ENCODING = {
|
6
|
+
gsm: {
|
7
|
+
normal: 160,
|
8
|
+
concatenated: 153,
|
9
|
+
},
|
10
|
+
unicode: {
|
11
|
+
normal: 70,
|
12
|
+
concatenated: 67,
|
13
|
+
},
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
attr :text
|
17
|
+
|
18
|
+
def initialize(text)
|
19
|
+
@text = text
|
20
|
+
end
|
21
|
+
|
22
|
+
def encoding
|
23
|
+
@encoding ||= GsmEncoding.valid?(text) ? :gsm : :unicode
|
24
|
+
end
|
25
|
+
|
26
|
+
def gsm?
|
27
|
+
encoding == :gsm
|
28
|
+
end
|
29
|
+
|
30
|
+
def unicode?
|
31
|
+
encoding == :unicode
|
32
|
+
end
|
33
|
+
|
34
|
+
def concatenated?
|
35
|
+
concatenated_parts > 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def concatenated_parts
|
39
|
+
if length <= MAX_LENGTH_FOR_ENCODING[encoding][:normal]
|
40
|
+
1
|
41
|
+
else
|
42
|
+
(length.to_f / MAX_LENGTH_FOR_ENCODING[encoding][:concatenated]).ceil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def maximum_length_for(concatenated_parts)
|
47
|
+
message_type = concatenated_parts > 1 ? :concatenated : :normal
|
48
|
+
|
49
|
+
concatenated_parts * MAX_LENGTH_FOR_ENCODING[encoding][message_type]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the number of symbols, which the given text will take up in an SMS
|
53
|
+
# message, taking into account any double-space symbols in the GSM 03.38
|
54
|
+
# encoding.
|
55
|
+
def length
|
56
|
+
length = text.length
|
57
|
+
length += text.chars.count { |char| GsmEncoding.double_byte?(char) } if gsm?
|
58
|
+
|
59
|
+
length
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module SmsTools
|
2
|
+
# UTF-8 to GSM-7 (GSM 03.38) mapping. Based on code from:
|
3
|
+
# https://github.com/threez/smspromote/blob/master/lib/smspromote/encoding.rb
|
4
|
+
module GsmEncoding
|
5
|
+
extend self
|
6
|
+
|
7
|
+
UTF8_TO_GSM_BASE_TABLE = {
|
8
|
+
0x0040 => "\x00", # COMMERCIAL AT
|
9
|
+
0x00A3 => "\x01", # POUND SIGN
|
10
|
+
0x0024 => "\x02", # DOLLAR SIGN
|
11
|
+
0x00A5 => "\x03", # YEN SIGN
|
12
|
+
0x00E8 => "\x04", # LATIN SMALL LETTER E WITH GRAVE
|
13
|
+
0x00E9 => "\x05", # LATIN SMALL LETTER E WITH ACUTE
|
14
|
+
0x00F9 => "\x06", # LATIN SMALL LETTER U WITH GRAVE
|
15
|
+
0x00EC => "\x07", # LATIN SMALL LETTER I WITH GRAVE
|
16
|
+
0x00F2 => "\x08", # LATIN SMALL LETTER O WITH GRAVE
|
17
|
+
0x00E7 => "\x09", # LATIN SMALL LETTER C WITH CEDILLA
|
18
|
+
0x000A => "\x0A", # LINE FEED
|
19
|
+
0x00D8 => "\x0B", # LATIN CAPITAL LETTER O WITH STROKE
|
20
|
+
0x00F8 => "\x0C", # LATIN SMALL LETTER O WITH STROKE
|
21
|
+
0x000D => "\x0D", # CARRIAGE RETURN
|
22
|
+
0x00C5 => "\x0E", # LATIN CAPITAL LETTER A WITH RING ABOVE
|
23
|
+
0x00E5 => "\x0F", # LATIN SMALL LETTER A WITH RING ABOVE
|
24
|
+
0x0394 => "\x10", # GREEK CAPITAL LETTER DELTA
|
25
|
+
0x005F => "\x11", # LOW LINE
|
26
|
+
0x03A6 => "\x12", # GREEK CAPITAL LETTER PHI
|
27
|
+
0x0393 => "\x13", # GREEK CAPITAL LETTER GAMMA
|
28
|
+
0x039B => "\x14", # GREEK CAPITAL LETTER LAMDA
|
29
|
+
0x03A9 => "\x15", # GREEK CAPITAL LETTER OMEGA
|
30
|
+
0x03A0 => "\x16", # GREEK CAPITAL LETTER PI
|
31
|
+
0x03A8 => "\x17", # GREEK CAPITAL LETTER PSI
|
32
|
+
0x03A3 => "\x18", # GREEK CAPITAL LETTER SIGMA
|
33
|
+
0x0398 => "\x19", # GREEK CAPITAL LETTER THETA
|
34
|
+
0x039E => "\x1A", # GREEK CAPITAL LETTER XI
|
35
|
+
0x00A0 => "\x1B", # ESCAPE TO EXTENSION TABLE
|
36
|
+
0x00C6 => "\x1C", # LATIN CAPITAL LETTER AE
|
37
|
+
0x00E6 => "\x1D", # LATIN SMALL LETTER AE
|
38
|
+
0x00DF => "\x1E", # LATIN SMALL LETTER SHARP S (German)
|
39
|
+
0x00C9 => "\x1F", # LATIN CAPITAL LETTER E WITH ACUTE
|
40
|
+
0x0020 => "\x20", # SPACE
|
41
|
+
0x0021 => "\x21", # EXCLAMATION MARK
|
42
|
+
0x0022 => "\x22", # QUOTATION MARK
|
43
|
+
0x0023 => "\x23", # NUMBER SIGN
|
44
|
+
0x00A4 => "\x24", # CURRENCY SIGN
|
45
|
+
0x0025 => "\x25", # PERCENT SIGN
|
46
|
+
0x0026 => "\x26", # AMPERSAND
|
47
|
+
0x0027 => "\x27", # APOSTROPHE
|
48
|
+
0x0028 => "\x28", # LEFT PARENTHESIS
|
49
|
+
0x0029 => "\x29", # RIGHT PARENTHESIS
|
50
|
+
0x002A => "\x2A", # ASTERISK
|
51
|
+
0x002B => "\x2B", # PLUS SIGN
|
52
|
+
0x002C => "\x2C", # COMMA
|
53
|
+
0x002D => "\x2D", # HYPHEN-MINUS
|
54
|
+
0x002E => "\x2E", # FULL STOP
|
55
|
+
0x002F => "\x2F", # SOLIDUS
|
56
|
+
0x0030 => "\x30", # DIGIT ZERO
|
57
|
+
0x0031 => "\x31", # DIGIT ONE
|
58
|
+
0x0032 => "\x32", # DIGIT TWO
|
59
|
+
0x0033 => "\x33", # DIGIT THREE
|
60
|
+
0x0034 => "\x34", # DIGIT FOUR
|
61
|
+
0x0035 => "\x35", # DIGIT FIVE
|
62
|
+
0x0036 => "\x36", # DIGIT SIX
|
63
|
+
0x0037 => "\x37", # DIGIT SEVEN
|
64
|
+
0x0038 => "\x38", # DIGIT EIGHT
|
65
|
+
0x0039 => "\x39", # DIGIT NINE
|
66
|
+
0x003A => "\x3A", # COLON
|
67
|
+
0x003B => "\x3B", # SEMICOLON
|
68
|
+
0x003C => "\x3C", # LESS-THAN SIGN
|
69
|
+
0x003D => "\x3D", # EQUALS SIGN
|
70
|
+
0x003E => "\x3E", # GREATER-THAN SIGN
|
71
|
+
0x003F => "\x3F", # QUESTION MARK
|
72
|
+
0x00A1 => "\x40", # INVERTED EXCLAMATION MARK
|
73
|
+
0x0041 => "\x41", # LATIN CAPITAL LETTER A
|
74
|
+
0x0042 => "\x42", # LATIN CAPITAL LETTER B
|
75
|
+
0x0043 => "\x43", # LATIN CAPITAL LETTER C
|
76
|
+
0x0044 => "\x44", # LATIN CAPITAL LETTER D
|
77
|
+
0x0045 => "\x45", # LATIN CAPITAL LETTER E
|
78
|
+
0x0046 => "\x46", # LATIN CAPITAL LETTER F
|
79
|
+
0x0047 => "\x47", # LATIN CAPITAL LETTER G
|
80
|
+
0x0048 => "\x48", # LATIN CAPITAL LETTER H
|
81
|
+
0x0049 => "\x49", # LATIN CAPITAL LETTER I
|
82
|
+
0x004A => "\x4A", # LATIN CAPITAL LETTER J
|
83
|
+
0x004B => "\x4B", # LATIN CAPITAL LETTER K
|
84
|
+
0x004C => "\x4C", # LATIN CAPITAL LETTER L
|
85
|
+
0x004D => "\x4D", # LATIN CAPITAL LETTER M
|
86
|
+
0x004E => "\x4E", # LATIN CAPITAL LETTER N
|
87
|
+
0x004F => "\x4F", # LATIN CAPITAL LETTER O
|
88
|
+
0x0050 => "\x50", # LATIN CAPITAL LETTER P
|
89
|
+
0x0051 => "\x51", # LATIN CAPITAL LETTER Q
|
90
|
+
0x0052 => "\x52", # LATIN CAPITAL LETTER R
|
91
|
+
0x0053 => "\x53", # LATIN CAPITAL LETTER S
|
92
|
+
0x0054 => "\x54", # LATIN CAPITAL LETTER T
|
93
|
+
0x0055 => "\x55", # LATIN CAPITAL LETTER U
|
94
|
+
0x0056 => "\x56", # LATIN CAPITAL LETTER V
|
95
|
+
0x0057 => "\x57", # LATIN CAPITAL LETTER W
|
96
|
+
0x0058 => "\x58", # LATIN CAPITAL LETTER X
|
97
|
+
0x0059 => "\x59", # LATIN CAPITAL LETTER Y
|
98
|
+
0x005A => "\x5A", # LATIN CAPITAL LETTER Z
|
99
|
+
0x00C4 => "\x5B", # LATIN CAPITAL LETTER A WITH DIAERESIS
|
100
|
+
0x00D6 => "\x5C", # LATIN CAPITAL LETTER O WITH DIAERESIS
|
101
|
+
0x00D1 => "\x5D", # LATIN CAPITAL LETTER N WITH TILDE
|
102
|
+
0x00DC => "\x5E", # LATIN CAPITAL LETTER U WITH DIAERESIS
|
103
|
+
0x00A7 => "\x5F", # SECTION SIGN
|
104
|
+
0x00BF => "\x60", # INVERTED QUESTION MARK
|
105
|
+
0x0061 => "\x61", # LATIN SMALL LETTER A
|
106
|
+
0x0062 => "\x62", # LATIN SMALL LETTER B
|
107
|
+
0x0063 => "\x63", # LATIN SMALL LETTER C
|
108
|
+
0x0064 => "\x64", # LATIN SMALL LETTER D
|
109
|
+
0x0065 => "\x65", # LATIN SMALL LETTER E
|
110
|
+
0x0066 => "\x66", # LATIN SMALL LETTER F
|
111
|
+
0x0067 => "\x67", # LATIN SMALL LETTER G
|
112
|
+
0x0068 => "\x68", # LATIN SMALL LETTER H
|
113
|
+
0x0069 => "\x69", # LATIN SMALL LETTER I
|
114
|
+
0x006A => "\x6A", # LATIN SMALL LETTER J
|
115
|
+
0x006B => "\x6B", # LATIN SMALL LETTER K
|
116
|
+
0x006C => "\x6C", # LATIN SMALL LETTER L
|
117
|
+
0x006D => "\x6D", # LATIN SMALL LETTER M
|
118
|
+
0x006E => "\x6E", # LATIN SMALL LETTER N
|
119
|
+
0x006F => "\x6F", # LATIN SMALL LETTER O
|
120
|
+
0x0070 => "\x70", # LATIN SMALL LETTER P
|
121
|
+
0x0071 => "\x71", # LATIN SMALL LETTER Q
|
122
|
+
0x0072 => "\x72", # LATIN SMALL LETTER R
|
123
|
+
0x0073 => "\x73", # LATIN SMALL LETTER S
|
124
|
+
0x0074 => "\x74", # LATIN SMALL LETTER T
|
125
|
+
0x0075 => "\x75", # LATIN SMALL LETTER U
|
126
|
+
0x0076 => "\x76", # LATIN SMALL LETTER V
|
127
|
+
0x0077 => "\x77", # LATIN SMALL LETTER W
|
128
|
+
0x0078 => "\x78", # LATIN SMALL LETTER X
|
129
|
+
0x0079 => "\x79", # LATIN SMALL LETTER Y
|
130
|
+
0x007A => "\x7A", # LATIN SMALL LETTER Z
|
131
|
+
0x00E4 => "\x7B", # LATIN SMALL LETTER A WITH DIAERESIS
|
132
|
+
0x00F6 => "\x7C", # LATIN SMALL LETTER O WITH DIAERESIS
|
133
|
+
0x00F1 => "\x7D", # LATIN SMALL LETTER N WITH TILDE
|
134
|
+
0x00FC => "\x7E", # LATIN SMALL LETTER U WITH DIAERESIS
|
135
|
+
0x00E0 => "\x7F", # LATIN SMALL LETTER A WITH GRAVE
|
136
|
+
}.freeze
|
137
|
+
|
138
|
+
UTF8_TO_GSM_EXTENSION_TABLE = {
|
139
|
+
0x000C => "\x1B\x0A", # FORM FEED
|
140
|
+
0x005E => "\x1B\x14", # CIRCUMFLEX ACCENT
|
141
|
+
0x007B => "\x1B\x28", # LEFT CURLY BRACKET
|
142
|
+
0x007D => "\x1B\x29", # RIGHT CURLY BRACKET
|
143
|
+
0x005C => "\x1B\x2F", # REVERSE SOLIDUS
|
144
|
+
0x005B => "\x1B\x3C", # LEFT SQUARE BRACKET
|
145
|
+
0x007E => "\x1B\x3D", # TILDE
|
146
|
+
0x005D => "\x1B\x3E", # RIGHT SQUARE BRACKET
|
147
|
+
0x007C => "\x1B\x40", # VERTICAL LINE
|
148
|
+
0x20AC => "\x1B\x65" # EURO SIGN
|
149
|
+
}.freeze
|
150
|
+
|
151
|
+
UTF8_TO_GSM = UTF8_TO_GSM_BASE_TABLE.merge(UTF8_TO_GSM_EXTENSION_TABLE).freeze
|
152
|
+
GSM_TO_UTF8 = UTF8_TO_GSM.invert.freeze
|
153
|
+
|
154
|
+
def valid?(utf8_encoded_string)
|
155
|
+
utf8_encoded_string.unpack('U*').all? { |char| UTF8_TO_GSM[char] }
|
156
|
+
end
|
157
|
+
|
158
|
+
def double_byte?(char)
|
159
|
+
UTF8_TO_GSM_EXTENSION_TABLE[char.unpack('U').first]
|
160
|
+
end
|
161
|
+
|
162
|
+
def from_utf8(utf8_encoded_string)
|
163
|
+
gsm_encoded_string = ''
|
164
|
+
|
165
|
+
utf8_encoded_string.unpack('U*').each do |char|
|
166
|
+
if converted = UTF8_TO_GSM[char]
|
167
|
+
gsm_encoded_string << converted
|
168
|
+
else
|
169
|
+
raise "Unsupported symbol in GSM-7 encoding: 0x#{char.to_s(16).upcase}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
gsm_encoded_string
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_utf8(gsm_encoded_string)
|
177
|
+
utf8_encoded_string = ''
|
178
|
+
escape = false
|
179
|
+
escape_code = "\e".freeze
|
180
|
+
|
181
|
+
gsm_encoded_string.each_char do |char|
|
182
|
+
if char == escape_code
|
183
|
+
escape = true
|
184
|
+
elsif escape
|
185
|
+
escape = false
|
186
|
+
utf8_encoded_string << [GSM_TO_UTF8[escape_code + char]].pack('U')
|
187
|
+
else
|
188
|
+
utf8_encoded_string << [GSM_TO_UTF8[char]].pack('U')
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
utf8_encoded_string
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/lib/smstools.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'sms_tools'
|
data/smstools.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sms_tools/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'smstools'
|
8
|
+
spec.version = SmsTools::VERSION
|
9
|
+
spec.authors = ['Dimitar Dimitrov']
|
10
|
+
spec.email = ['me@ddimitrov.name']
|
11
|
+
spec.summary = 'Small library of classes for common SMS-related functionality.'
|
12
|
+
spec.description = 'Features SMS text encoding detection, length counting, concatenation detection and more. Can be used with or without Rails. Requires Ruby 1.9 or newer.'
|
13
|
+
spec.homepage = 'https://github.com/mitio/smstools'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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 'bundler', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'minitest'
|
24
|
+
spec.add_development_dependency 'minitest-ansi'
|
25
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sms_tools/encoding_detection'
|
3
|
+
|
4
|
+
describe SmsTools::EncodingDetection do
|
5
|
+
it "exposes the original text as a method" do
|
6
|
+
detection_for('foo').text.must_equal 'foo'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "encoding" do
|
10
|
+
it "defaults to GSM encoding for empty messages" do
|
11
|
+
detection_for('').encoding.must_equal :gsm
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns GSM as encoding for simple ASCII text" do
|
15
|
+
detection_for('foo bar baz').encoding.must_equal :gsm
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns GSM as encoding for special symbols defined in GSM 03.38" do
|
19
|
+
detection_for('09azAZ@Δ¡¿£_!Φ"¥Γ#èΛ¤éΩ%ùΠ&ìΨòΣçΘΞ:Ø;ÄäøÆ,<Ööæ=ÑñÅß>Üüåɧà€~').encoding.must_equal :gsm
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns GSM as encoding for puntucation and newline symbols" do
|
23
|
+
detection_for('Foo bar {} [baz]! Larodi $5. What else?').encoding.must_equal :gsm
|
24
|
+
detection_for("Spaces and newlines are GSM 03.38, too: \r\n").encoding.must_equal :gsm
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns Unicode when non-GSM Unicode symbols are used" do
|
28
|
+
detection_for('Foo bar лароди').encoding.must_equal :unicode
|
29
|
+
detection_for('∞').encoding.must_equal :unicode
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "message length" do
|
34
|
+
it "computes the length of trivial ANSI-only messages correctly" do
|
35
|
+
detection_for('').length.must_equal 0
|
36
|
+
detection_for('larodi').length.must_equal 6
|
37
|
+
detection_for('a' * 180).length.must_equal 180
|
38
|
+
end
|
39
|
+
|
40
|
+
it "computes the length of non-trivial GSM encoded messages correctly" do
|
41
|
+
detection_for('GSM: 09azAZ@Δ¡¿£_!Φ"¥Γ#èΛ¤éΩ%ùΠ&ìΨòΣçΘΞ:Ø;ÄäøÆ,<Ööæ=ÑñÅß>Üüåɧà').length.must_equal 63
|
42
|
+
end
|
43
|
+
|
44
|
+
it "correctly counts the length of whitespace-only messages" do
|
45
|
+
detection_for(' ').length.must_equal 5
|
46
|
+
detection_for("\r\n ").length.must_equal 4
|
47
|
+
end
|
48
|
+
|
49
|
+
it "correctly counts the length of whitespace chars in GSM-encoded messages" do
|
50
|
+
detection_for('ΞØ ').length.must_equal 7
|
51
|
+
detection_for("ΞØ\r\n ").length.must_equal 6
|
52
|
+
end
|
53
|
+
|
54
|
+
it "counts double-space chars for GSM encoding" do
|
55
|
+
detection_for('^{}[~]|€\\').length.must_equal 18
|
56
|
+
detection_for('Σ: €').length.must_equal 5
|
57
|
+
end
|
58
|
+
|
59
|
+
it "computes the length of Unicode messages correctly" do
|
60
|
+
detection_for('кирилица').length.must_equal 8
|
61
|
+
detection_for('Я!').length.must_equal 2
|
62
|
+
detection_for("Уникод: ΞØ\r\n ").length.must_equal 14
|
63
|
+
detection_for('Ю' * 200).length.must_equal 200
|
64
|
+
end
|
65
|
+
|
66
|
+
it "doesn't count double-space chars for Unicode encoding" do
|
67
|
+
detection_for('Уникод: ^{}[~]|€\\').length.must_equal 17
|
68
|
+
detection_for('Уникод: Σ: €').length.must_equal 12
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "concatenated message parts counting" do
|
73
|
+
def concatenated_parts_for(length: nil, encoding: nil, must_be: nil)
|
74
|
+
SmsTools::EncodingDetection.new('').stub :length, length do |detection|
|
75
|
+
detection.stub :encoding, encoding do |detection|
|
76
|
+
detection.concatenated_parts.must_equal must_be
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "counts parts for GSM-encoded messages" do
|
82
|
+
concatenated_parts_for length: 0, encoding: :gsm, must_be: 1
|
83
|
+
concatenated_parts_for length: 160, encoding: :gsm, must_be: 1
|
84
|
+
concatenated_parts_for length: 161, encoding: :gsm, must_be: 2
|
85
|
+
concatenated_parts_for length: 306, encoding: :gsm, must_be: 2
|
86
|
+
concatenated_parts_for length: 307, encoding: :gsm, must_be: 3
|
87
|
+
concatenated_parts_for length: 459, encoding: :gsm, must_be: 3
|
88
|
+
concatenated_parts_for length: 500, encoding: :gsm, must_be: 4
|
89
|
+
end
|
90
|
+
|
91
|
+
it "counts parts for Unicode messages" do
|
92
|
+
concatenated_parts_for length: 0, encoding: :unicode, must_be: 1
|
93
|
+
concatenated_parts_for length: 70, encoding: :unicode, must_be: 1
|
94
|
+
concatenated_parts_for length: 71, encoding: :unicode, must_be: 2
|
95
|
+
concatenated_parts_for length: 134, encoding: :unicode, must_be: 2
|
96
|
+
concatenated_parts_for length: 135, encoding: :unicode, must_be: 3
|
97
|
+
end
|
98
|
+
|
99
|
+
it "counts parts for actual GSM-encoded and Unicode messages" do
|
100
|
+
detection_for('').concatenated_parts.must_equal 1
|
101
|
+
detection_for('Я').concatenated_parts.must_equal 1
|
102
|
+
detection_for('Σ' * 160).concatenated_parts.must_equal 1
|
103
|
+
detection_for('Σ' * 159 + '~').concatenated_parts.must_equal 2
|
104
|
+
detection_for('Я' * 133 + '~').concatenated_parts.must_equal 2
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "maximum length for particular number of concatenated messages" do
|
109
|
+
it "works for GSM-encoded messages" do
|
110
|
+
detection_for('x').maximum_length_for(1).must_equal 160
|
111
|
+
detection_for('x').maximum_length_for(2).must_equal 306
|
112
|
+
detection_for('x').maximum_length_for(3).must_equal 459
|
113
|
+
end
|
114
|
+
|
115
|
+
it "works for Unicode messages" do
|
116
|
+
detection_for('ю').maximum_length_for(1).must_equal 70
|
117
|
+
detection_for('ю').maximum_length_for(2).must_equal 134
|
118
|
+
detection_for('ю').maximum_length_for(3).must_equal 201
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "predicates" do
|
123
|
+
it "returns true for gsm? and false for unicode? if the encoding is GSM" do
|
124
|
+
detection_for('').stub :encoding, :gsm do |detection|
|
125
|
+
detection.must_be :gsm?
|
126
|
+
detection.wont_be :unicode?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns false for gsm? and true for unicode? if the encoding is Unicode" do
|
131
|
+
detection_for('').stub :encoding, :unicode do |detection|
|
132
|
+
detection.wont_be :gsm?
|
133
|
+
detection.must_be :unicode?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "returns true for concatenated? if concatenated_parts > 1" do
|
138
|
+
detection_for('').stub :concatenated_parts, 7 do |detection|
|
139
|
+
detection.must_be :concatenated?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "returns false for concatenated? if concatenated_parts is 1" do
|
144
|
+
detection_for('').stub :concatenated_parts, 1 do |detection|
|
145
|
+
detection.wont_be :concatenated?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def detection_for(text)
|
151
|
+
SmsTools::EncodingDetection.new text
|
152
|
+
end
|
153
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smstools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dimitar Dimitrov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-ansi
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Features SMS text encoding detection, length counting, concatenation
|
70
|
+
detection and more. Can be used with or without Rails. Requires Ruby 1.9 or newer.
|
71
|
+
email:
|
72
|
+
- me@ddimitrov.name
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- CHANGELOG.md
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/assets/javascripts/sms_tools/index.js.coffee
|
84
|
+
- lib/assets/javascripts/sms_tools/message.js.coffee
|
85
|
+
- lib/sms_tools.rb
|
86
|
+
- lib/sms_tools/encoding_detection.rb
|
87
|
+
- lib/sms_tools/gsm_encoding.rb
|
88
|
+
- lib/sms_tools/rails/engine.rb
|
89
|
+
- lib/sms_tools/version.rb
|
90
|
+
- lib/smstools.rb
|
91
|
+
- smstools.gemspec
|
92
|
+
- spec/sms_tools/encoding_detection_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
homepage: https://github.com/mitio/smstools
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.2.0
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Small library of classes for common SMS-related functionality.
|
118
|
+
test_files:
|
119
|
+
- spec/sms_tools/encoding_detection_spec.rb
|
120
|
+
- spec/spec_helper.rb
|