string-direction 0.0.4 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f39604302e8df7944be6b2e1797f74570597820
4
- data.tar.gz: ad31b8233d04f4e6a4cc0950f417c053bf7da18f
3
+ metadata.gz: 0ef6e54736486fccff8d5e5c004d73c27a5ca7b4
4
+ data.tar.gz: d16b4d813ef25aa3cbd032af62a2861cf5fe0fe6
5
5
  SHA512:
6
- metadata.gz: 9bd9e95fda3d57c5c73d6a3a62d9133f3f4bb201de767b0f36a2b5475b41f8f4ce24abc14b2f1325e8d1d42c2905673fdd1432f135142a83e80956886221ac24
7
- data.tar.gz: ce4026eadff9db6f4a1d9e9ccd8f131b3f84c18d8e70c4c1eaba88a135132514345bc970cdeefbf2914951c73d63266515076ca34231781ecf7516e1a92001ab
6
+ metadata.gz: 72c1db5ba8b6d1f3cfffe46315a8956a34d41f957a4ebad2e09daad798dcf590c90a06947115caa696e9c32f2eaaf61114e101d621ed5d8a27b53267ad3f50c0
7
+ data.tar.gz: 4424cab8cab4d1b9e016169867a1cf0c8b050ce6f498727c234a89414ebbad36b24a704abf6c84c15ef55b251064379918c42f0ffbd436b006dd08fa0f7d1bb9
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  .yardoc/
2
2
  *gem
3
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in algorithm.gemspec
4
+ gemspec
data/README.md CHANGED
@@ -1,32 +1,53 @@
1
1
  # string-direction
2
2
 
3
- `string-direction` is a ruby `String` extension for automatic detection of the direction (left-to-right, right-to-left or bi-directional) in which a text should be displayed.
3
+ `string-direction` is a ruby library for automatic detection of the direction (left-to-right, right-to-left or bi-directional) in which a text should be displayed.
4
4
 
5
- Mainly, it exposes four new methods to `String` instances. `direction` can return `ltr`, `rtl` or `bidi` depending on the characters used in the string. There are as well `is_ltr?`, `is_rtl?` and `is_bidi?` convenient predicate methods.
5
+ ## Overview
6
6
 
7
7
  ```ruby
8
- #encoding: utf-8
9
8
  require 'string-direction'
10
9
 
11
- p 'english'.direction #=> "ltr"
12
- p 'العربية'.direction #=> "rtl"
13
- p 'english العربية'.direction #=> "bidi"
10
+ detector = StringDirection::Detector.new
14
11
 
15
- p 'english'.is_ltr? #=> "true"
16
- p 'العربية'.is_ltr? #=> "false"
17
- p 'العربية'.is_rtl? #=> "true"
18
- p 'english العربية'.is_bidi? #=> "true"
12
+ detector.direction('english') #=> 'ltr'
13
+ detector.direction('العربية') #=> 'rtl'
14
+ detector.direction("english العربية") #=> 'bidi'
15
+
16
+ detector.ltr?('english') #=> true
17
+ detector.rtl?('العربية') #=> true
18
+ detector.bidi?('english') #=> false
19
19
  ```
20
20
 
21
- ## Unicode marks
22
- If Unicode marks [left-to-right](http://en.wikipedia.org/wiki/Left-to-right_mark) (\u200e) or [right-to-left](http://en.wikipedia.org/wiki/Right-to-left_mark) (\u200f) are present, then `string-direction` rely on them instead of trying to guess from the characters used.
21
+ But, if you preffer, you can monkey patch `String`:
23
22
 
24
23
  ```ruby
25
- p "\u200eالعربية".direction #=> "ltr"
26
- p "\u200fEnglish".direction #=> "rtl"
24
+ String.send(:include, StringDirection::StringMethods)
25
+
26
+ 'english'.direction #=> 'ltr'
27
+ 'العربية'.rtl? #=> true
27
28
  ```
28
29
 
29
- ## Changing default right-to-left scripts
30
+ ## Strategies
31
+
32
+ `string-direction` uses different strategies in order to try to detect the direction of a string. The detector uses them once at a time and returns the result once one of them succeeds, aborting any further analysis.
33
+
34
+ Right now, two strategies are natively integrated: `marks` and `characters`. They are used, in that order, as default strategies if no strategies are given.
35
+
36
+ ### marks
37
+
38
+ Looks for the presence of direction Unicode marks: [left-to-right](http://en.wikipedia.org/wiki/Left-to-right_mark) (\u200e) or [right-to-left](http://en.wikipedia.org/wiki/Right-to-left_mark) (\u200f).
39
+
40
+ ```ruby
41
+ detector = StringDirection::Detector.new(:marks)
42
+
43
+ detector.direction("\u200eالعربية") #=> "ltr"
44
+ detector.direction("\u200fEnglish") #=> "rtl"
45
+ ```
46
+
47
+ ### characters
48
+
49
+ Looks for the presence of right-to-left characters in the scripts used in the string.
50
+
30
51
  By default, `string-direction` consider following scripts to have a right-to-left writing:
31
52
 
32
53
  * Arabic
@@ -38,12 +59,23 @@ By default, `string-direction` consider following scripts to have a right-to-lef
38
59
  * Thaana
39
60
  * Tifinagh
40
61
 
41
- You can change easily these defaults interacting with the array `StringDirection.rtl_scripts`
62
+ ```ruby
63
+ detector = StringDirection::Detector.new(:characters)
64
+
65
+ detector.direction('english') #=> 'ltr'
66
+ detector.direction('العربية') #=> 'rtl'
67
+ ```
68
+
69
+ You can change these defaults:
42
70
 
43
71
  ```ruby
44
- p 'ᚪᚫᚬᚭᚮᚯ'.direction #=> "ltr"
45
- StringDirection.rtl_scripts << 'Runic'
46
- p 'ᚪᚫᚬᚭᚮᚯ'.direction #=> "rtl"
72
+ detector.direction('ᚪᚫᚬᚭᚮᚯ') #=> 'ltr'
73
+
74
+ StringDirection.configuration do |config|
75
+ config.rtl_scripts << 'Runic'
76
+ end
77
+
78
+ detector.direction('ᚪᚫᚬᚭᚮᚯ') #=> 'rtl'
47
79
  ```
48
80
 
49
81
  This can be useful, mainly, for scripts that have both left-to-right and right-to-left representations:
@@ -58,6 +90,31 @@ This can be useful, mainly, for scripts that have both left-to-right and right-t
58
90
 
59
91
  Keep in mind than only [scripts recognized by Ruby regular expressions](http://www.ruby-doc.org/core-1.9.3/Regexp.html#label-Character+Properties) are allowed.
60
92
 
93
+ ### Custom Strategies
94
+
95
+ You can define your custom strategies. To do so, you just have to define a class inside `StringDirection` module with a name ending with `Strategy`. This class has to respond to an instance method `run` which takes the string as argument. You can inherit from `StringDirection::Strategy` to have convenient methods `ltr`, `rtl` and `bidi`.
96
+
97
+ ```ruby
98
+ class StringDirection::AlwaysLtrStrategy
99
+ def run(string)
100
+ ltr
101
+ end
102
+ end
103
+
104
+ detector = StringDirection::Detector.new(:always_ltr)
105
+ detector.direction('العربية') #=> 'ltr'
106
+ ```
107
+
108
+ ### Changing default strategies
109
+
110
+ `marks` and `characters` are default strategies, but you can change them:
111
+
112
+ ```ruby
113
+ StringDirection.configuration do |config|
114
+ config.default_strategies = [:custom, :marks, :always_ltr]
115
+ end
116
+ ```
117
+
61
118
  ## Release Policy
62
119
 
63
120
  `string-direction` follows the principles of [semantic versioning](http://semver.org/).
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'string-direction'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require 'pry'
11
+ Pry.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -1,3 +1,39 @@
1
- require_relative 'string-direction/string-direction'
1
+ require 'string-direction/version'
2
+ require 'string-direction/configuration'
3
+ require 'string-direction/detector'
4
+ require 'string-direction/strategy'
5
+ require 'string-direction/strategies/marks_strategy'
6
+ require 'string-direction/strategies/characters_strategy'
7
+ require 'string-direction/string_methods'
2
8
 
3
- String.send :include, StringDirection
9
+ # Constants & configuration common in the whole library
10
+ module StringDirection
11
+ # left-to-right identifier
12
+ LTR = 'ltr'.freeze
13
+
14
+ # right-to-left identifier
15
+ RTL = 'rtl'.freeze
16
+
17
+ # bidi identifier
18
+ BIDI = 'bidi'.freeze
19
+
20
+ class << self
21
+ # {Configuration} object
22
+ attr_accessor :configuration
23
+
24
+ def configuration
25
+ @configuration ||= Configuration.new
26
+ end
27
+
28
+ # Yields current {Configuration}
29
+ def configure
30
+ self.configuration ||= Configuration.new
31
+ yield(configuration)
32
+ end
33
+
34
+ # Reset {Configuration}
35
+ def reset_configuration
36
+ self.configuration = Configuration.new
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ module StringDirection
2
+ # {StringDirection} configuration
3
+ class Configuration
4
+ # Scripts for which characters are treated as right-to-left. Defaults to Arabic, Hebrew, Nko, Kharoshthi, Phoenician, Syriac, Thaana and Tifinagh. Notice than only {http://ruby-doc.org/core-2.2.3/Regexp.html#class-Regexp-label-Character+Properties recognized Ruby regular expression scripts} are accepted.
5
+ #
6
+ # @return [Array]
7
+ attr_accessor :rtl_scripts
8
+
9
+ # Default strategies, in order, that {Detector} uses if they are not given explicetly. Values are symbols with a matching class expected. For example, `:marks` expects a class `StringDirection::MarksStrategy` to exist. Defaults to `:marks` and `:characters`.
10
+ #
11
+ # @return [Array]
12
+ attr_accessor :default_strategies
13
+
14
+ # Initialize defaults
15
+ def initialize
16
+ self.rtl_scripts = %w(Arabic Hebrew Nko Kharoshthi Phoenician Syriac Thaana Tifinagh)
17
+ self.default_strategies = [:marks, :characters]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,72 @@
1
+ module StringDirection
2
+ # String direction detector
3
+ class Detector
4
+ # Array of initialized strategies used, in order, to try to detect the direction of a string
5
+ #
6
+ # @return [Array]
7
+ attr_accessor :strategies
8
+
9
+ # Initialize strategies from given arguments. If no strategies are given, they are taken from the value of {StringDirection::Configuration#default_strategies}
10
+ #
11
+ # @raise [ArgumentError] if strategy class is not found. For example, for an strategy `:marks` a class `StringDirection::MarksStrategy` is expected
12
+ def initialize(*strategies)
13
+ strategies = StringDirection.configuration.default_strategies if strategies.empty?
14
+ initialize_strategies(strategies)
15
+ end
16
+
17
+ # Tries to detect and return the direction of a string. It returns `ltr` if the string is left-to-right, `rtl` if it is right-to-left, `bidi` if it is bidirectional or `nil` if it can't detect the direction. It iterates through {#strategies} until one of them successes.
18
+ #
19
+ # @param string [String] The string to inspect
20
+ # @return [String, nil]
21
+ def direction(string)
22
+ direction = nil
23
+ strategies.each do |strategy|
24
+ direction = strategy.run(string)
25
+ break if direction
26
+ end
27
+ direction
28
+ end
29
+
30
+ # Returns whether string is left-to-right or not
31
+ #
32
+ # @param string [String] The string to inspect
33
+ # @return [Boolean]
34
+ def ltr?(string)
35
+ direction(string) == StringDirection::LTR
36
+ end
37
+
38
+ # Returns whether string is right-to-left or not
39
+ #
40
+ # @param string [String] The string to inspect
41
+ # @return [Boolean]
42
+ def rtl?(string)
43
+ direction(string) == StringDirection::RTL
44
+ end
45
+
46
+ # Returns whether string is bidirectional or not
47
+ #
48
+ # @param string [String] The string to inspect
49
+ # @return [Boolean]
50
+ def bidi?(string)
51
+ direction(string) == StringDirection::BIDI
52
+ end
53
+
54
+ private
55
+
56
+ def initialize_strategies(strategies)
57
+ self.strategies = strategies.map do |strategy|
58
+ begin
59
+ name = infer_strategy_class_name(strategy)
60
+ Kernel.const_get(name).new
61
+ rescue NameError
62
+ raise ArgumentError, "Can't find '#{name}' strategy"
63
+ end
64
+ end
65
+ end
66
+
67
+ def infer_strategy_class_name(strategy)
68
+ base_name = strategy.to_s.split('_').map(&:capitalize).join
69
+ "StringDirection::#{base_name}Strategy"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,40 @@
1
+ module StringDirection
2
+ # Strategy to detect direction from the scripts to which string characters belong
3
+ class CharactersStrategy < Strategy
4
+ # Ignored characters: unicode marks, punctuations, symbols, separator and other general categories
5
+ CHAR_IGNORE_REGEX = /[\p{M}\p{P}\p{S}\p{Z}\p{C}]/.freeze
6
+
7
+ # Inspect to wich scripts characters belongs and infer from them the string direction. right-to-left scripts are those in {Configuration#rtl_scripts}
8
+ #
9
+ # params [String] The string to inspect
10
+ # @return [String, nil]
11
+ def run(string)
12
+ string = string.to_s
13
+ if ltr_characters?(string) && rtl_characters?(string)
14
+ bidi
15
+ elsif ltr_characters?(string)
16
+ ltr
17
+ elsif rtl_characters?(string)
18
+ rtl
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def rtl_characters?(string)
25
+ string.match(/[#{join_scripts_for_regexp(rtl_scripts)}]/) ? true : false
26
+ end
27
+
28
+ def ltr_characters?(string)
29
+ string.gsub(CHAR_IGNORE_REGEX, '').match(/[^#{join_scripts_for_regexp(rtl_scripts)}]/) ? true : false
30
+ end
31
+
32
+ def join_scripts_for_regexp(scripts)
33
+ scripts.map { |script| '\p{' + script + '}' }.join
34
+ end
35
+
36
+ def rtl_scripts
37
+ StringDirection.configuration.rtl_scripts
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ module StringDirection
2
+ # Strategy to detect direction looking for the presence of unicode marks
3
+ class MarksStrategy < Strategy
4
+ # left-to-right unicode mark
5
+ LTR_MARK = "\u200e".freeze
6
+
7
+ # right-to-right unicode mark
8
+ RTL_MARK = "\u200f".freeze
9
+
10
+ # Look for the presence of unicode marks in given string and infers from them its direction
11
+ #
12
+ # params [String] The string to inspect
13
+ # @return [String, nil]
14
+ def run(string)
15
+ string = string.to_s
16
+ if ltr_mark?(string) && rtl_mark?(string)
17
+ bidi
18
+ elsif ltr_mark?(string)
19
+ ltr
20
+ elsif rtl_mark?(string)
21
+ rtl
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def ltr_mark?(string)
28
+ string.include?(LTR_MARK) ? true : false
29
+ end
30
+
31
+ def rtl_mark?(string)
32
+ string.include?(RTL_MARK) ? true : false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module StringDirection
2
+ # @abstract Subclass and override {#run} to implement
3
+ class Strategy
4
+ # Each strategy must implement this method, accepting an string as its argument. It must return {StringDirection::LTR}, {StringDirection::RTL}, {StringDirection::BIDI} depending on direction detected, or nil on detection failure
5
+ # @abstract
6
+ # @raise [NotImplementedError]
7
+ def run(string)
8
+ fail NotImplementedError, "`run` method must be implemented"
9
+ end
10
+
11
+ private
12
+
13
+ def ltr
14
+ StringDirection::LTR
15
+ end
16
+
17
+ def rtl
18
+ StringDirection::RTL
19
+ end
20
+
21
+ def bidi
22
+ StringDirection::BIDI
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ module StringDirection
2
+ # Methods intended to be monkey patched to String through `String.include(StringDirection::StringMethods)`. This will allow stuff like `'English'.direction #=> 'ltr'`. All methods are delegated to {Detector} with `self` as string argument.
3
+ module StringMethods
4
+ # @see Detector#direction
5
+ # @return [String]
6
+ def direction
7
+ string_direction_detector.direction(self)
8
+ end
9
+
10
+ # @see Detector#ltr?
11
+ def ltr?
12
+ string_direction_detector.ltr?(self)
13
+ end
14
+
15
+ # @see Detector#rtl?
16
+ def rtl?
17
+ string_direction_detector.rtl?(self)
18
+ end
19
+
20
+ # @see Detector#bidi?
21
+ def bidi?
22
+ string_direction_detector.bidi?(self)
23
+ end
24
+
25
+ private
26
+
27
+ def string_direction_detector
28
+ @string_direction_detector ||= StringDirection::Detector.new
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ module StringDirection
2
+ # Current library version
3
+ VERSION = '1.0.0'
4
+ end
@@ -1,5 +1,8 @@
1
- require_relative '../lib/string-direction'
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'string-direction'
3
+ require 'pry'
2
4
 
3
- RSpec.configure do |c|
4
- c.treat_symbols_as_metadata_keys_with_true_values = true
5
+ Dir[File.join(__dir__, 'support/**/*.rb')].each { |f| require f }
6
+ RSpec.configure do |config|
7
+ config.color = true
5
8
  end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDirection::Configuration do
4
+ describe '#rtl_scripts' do
5
+ it 'defaults to an array with Arabic, Hebrew, Nko, Kharoshthi, Phoenician, Syriac, Thaana and Tifinagh' do
6
+ expect(subject.rtl_scripts).to match_array(%w(Arabic Hebrew Nko Kharoshthi Phoenician Syriac Thaana Tifinagh))
7
+ end
8
+ end
9
+
10
+ describe '#default_strategies' do
11
+ it 'defaults to an array with :marks and :characters' do
12
+ expect(subject.default_strategies).to eq([:marks, :characters])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDirection::Detector do
4
+ module StringDirection
5
+ class LtrStrategy < Strategy
6
+ def run(string)
7
+ ltr
8
+ end
9
+ end
10
+
11
+ class RtlStrategy < Strategy
12
+ def run(string)
13
+ rtl
14
+ end
15
+ end
16
+
17
+ class BidiStrategy < Strategy
18
+ def run(string)
19
+ bidi
20
+ end
21
+ end
22
+
23
+ class NilStrategy < Strategy
24
+ def run(string)
25
+ nil
26
+ end
27
+ end
28
+
29
+ class CamelizeCamelizeStrategy < Strategy
30
+ def run(string)
31
+ nil
32
+ end
33
+ end
34
+ end
35
+
36
+ context '#initialize(*strategies)' do
37
+ it 'initializes the strategies inflected from the arguments and adds them, in the same order, as strategies instance var array' do
38
+ detector = described_class.new(:ltr, :nil)
39
+
40
+ expect(detector.strategies.first).to be_an_instance_of(StringDirection::LtrStrategy)
41
+ expect(detector.strategies.last).to be_an_instance_of(StringDirection::NilStrategy)
42
+ end
43
+
44
+ it 'infers strategy name camelizing, ending with "Strategy" and looking inside StringDirection module' do
45
+ detector = described_class.new(:camelize_camelize)
46
+
47
+ expect(detector.strategies.first).to be_an_instance_of(StringDirection::CamelizeCamelizeStrategy)
48
+ end
49
+
50
+ context "when it can't infer the strategy class from given symbol" do
51
+ it 'raises an ArgumentError' do
52
+ expect { described_class.new(:something) }.to raise_error(ArgumentError)
53
+ end
54
+ end
55
+
56
+ context 'when stragies are not given' do
57
+ it 'takes defaults set in default_strategies configuration option' do
58
+ allow(StringDirection.configuration).to receive(:default_strategies).and_return([:ltr])
59
+
60
+ detector = described_class.new
61
+
62
+ expect(detector.strategies.first).to be_an_instance_of(StringDirection::LtrStrategy)
63
+ end
64
+ end
65
+ end
66
+
67
+ context '#direction(string)' do
68
+ context 'when first strategy detects direction' do
69
+ it 'returns it' do
70
+ detector = described_class.new(:ltr, :rtl)
71
+
72
+ expect(detector.direction('abc')).to eq(StringDirection::LTR)
73
+ end
74
+ end
75
+
76
+ context 'when first strategy does not detect direction' do
77
+ it 'it tries with the second' do
78
+ detector = described_class.new(:nil, :rtl)
79
+
80
+ expect(detector.direction('abc')).to eq(StringDirection::RTL)
81
+ end
82
+ end
83
+
84
+ context 'when no strategy detects direction' do
85
+ it 'returns nil' do
86
+ detector = described_class.new(:nil)
87
+
88
+ expect(detector.direction('abc')).to be_nil
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#ltr?(string)' do
94
+ context 'when string has left-to-right direction' do
95
+ it 'returns true' do
96
+ detector = described_class.new(:ltr)
97
+
98
+ expect(detector.ltr?('abc')).to eq(true)
99
+ end
100
+ end
101
+
102
+ context 'when string has not left-to-right direction' do
103
+ it 'returns false' do
104
+ detector = described_class.new(:rtl)
105
+
106
+ expect(detector.ltr?('abc')).to eq(false)
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '#rtl?(string)' do
112
+ context 'when string has right-to-left direction' do
113
+ it 'returns true' do
114
+ detector = described_class.new(:rtl)
115
+
116
+ expect(detector.rtl?('abc')).to eq(true)
117
+ end
118
+ end
119
+
120
+ context 'when string has not right-to-left direction' do
121
+ it 'returns false' do
122
+ detector = described_class.new(:bidi)
123
+
124
+ expect(detector.rtl?('abc')).to eq(false)
125
+ end
126
+ end
127
+ end
128
+
129
+ describe '#bidi?(string)' do
130
+ context 'when string has bidirectional direction' do
131
+ it 'returns true' do
132
+ detector = described_class.new(:bidi)
133
+
134
+ expect(detector.bidi?('abc')).to eq(true)
135
+ end
136
+ end
137
+
138
+ context 'when string has not bidirectional direction' do
139
+ it 'returns false' do
140
+ detector = described_class.new(:ltr)
141
+
142
+ expect(detector.bidi?('abc')).to eq(false)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDirection::CharactersStrategy do
4
+ describe '#run' do
5
+ let(:english) { 'English' }
6
+ let(:arabic) { 'العربية' }
7
+
8
+ subject { described_class.new.run(string) }
9
+
10
+ context 'when both left-to-right and right-to-left characters are present' do
11
+ let(:string) { arabic + english }
12
+
13
+ it "returns 'bidi'" do
14
+ expect(subject).to eq 'bidi'
15
+ end
16
+ end
17
+
18
+ context 'when right-to-left character are present but none of left-to-right' do
19
+ let(:string) { arabic }
20
+
21
+ it "returns 'rtl'" do
22
+ expect(subject).to eq 'rtl'
23
+ end
24
+ end
25
+
26
+ context 'when left-to-right character are present but none of right-to-left' do
27
+ let(:string) { english }
28
+
29
+ it "returns 'ltr'" do
30
+ expect(subject).to eq 'ltr'
31
+ end
32
+ end
33
+
34
+ context 'when neither left-to-right nor right-to-left characters are present' do
35
+ let(:string) { ' ' }
36
+
37
+ it 'returns nil' do
38
+ expect(subject).to be_nil
39
+ end
40
+ end
41
+
42
+ context 'when default right-to-left scripts are changed' do
43
+ let(:new_rtl_script) { 'Latin' }
44
+ let(:old_rtl_script) { 'Arabic' }
45
+
46
+ context 'when there are characters from an added right-to-left script' do
47
+ let(:string) { english }
48
+
49
+ it 'treats them as right-to-left chracters' do
50
+ StringDirection.configure do |config|
51
+ config.rtl_scripts << new_rtl_script
52
+ end
53
+
54
+ expect(subject).to eq 'rtl'
55
+ end
56
+ end
57
+
58
+ context 'when there are characters from a deleted right-to-left script ' do
59
+ let(:string) { arabic }
60
+
61
+ it 'treats them as left-to-right characters' do
62
+ StringDirection.configure do |config|
63
+ config.rtl_scripts.delete(old_rtl_script)
64
+ end
65
+
66
+ expect(subject).to eq 'ltr'
67
+ end
68
+ end
69
+
70
+ after :each do
71
+ StringDirection.reset_configuration
72
+ end
73
+ end
74
+
75
+ context 'when special characters are present' do
76
+ let(:string) do
77
+ mark = "\u0903"
78
+ punctuation = '_'
79
+ symbol = '€'
80
+ separator = ' '
81
+ other = "\u0005"
82
+
83
+ arabic + mark + punctuation + symbol + separator + other
84
+ end
85
+
86
+ it 'ignores them' do
87
+ expect(subject).to eq 'rtl'
88
+ end
89
+ end
90
+
91
+ context 'when an object responding to #to_s is given' do
92
+ let(:string) do
93
+ class StringDirection::TestObject
94
+ def to_s
95
+ 'English'
96
+ end
97
+ end
98
+
99
+ StringDirection::TestObject.new
100
+ end
101
+
102
+ it 'takes as string the result of #to_s method' do
103
+ expect(subject).to eq('ltr')
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDirection::MarksStrategy do
4
+ describe '#run' do
5
+ subject { described_class.new.run(string) }
6
+
7
+ context 'when string contains the left-to-right mark but not the right-to-left mark' do
8
+ let(:string) { described_class::LTR_MARK + 'abc' }
9
+
10
+ it "returns 'ltr'" do
11
+ expect(subject).to eql 'ltr'
12
+ end
13
+ end
14
+
15
+ context 'when string contains the right-to-left mark but not the left-to-right mark' do
16
+ let(:string) { described_class::RTL_MARK + 'abc' }
17
+
18
+ it "returns 'rtl'" do
19
+ expect(subject).to eql 'rtl'
20
+ end
21
+ end
22
+
23
+ context 'when string contains both the left-to-right mark and the right-to-left mark' do
24
+ let(:string) { described_class::LTR_MARK + described_class::RTL_MARK + 'abc' }
25
+
26
+ it "returns 'bidi'" do
27
+ expect(subject).to eql 'bidi'
28
+ end
29
+ end
30
+
31
+ context 'when string neither contains the left-to-right mark nor the right-to-left mark' do
32
+ let(:string) { 'abc' }
33
+
34
+ it "returns nil" do
35
+ expect(subject).to be_nil
36
+ end
37
+ end
38
+
39
+ context 'when an object responding to #to_s is given' do
40
+ let(:string) do
41
+ class StringDirection::TestObject
42
+ def to_s
43
+ StringDirection::MarksStrategy::LTR_MARK
44
+ end
45
+ end
46
+
47
+ StringDirection::TestObject.new
48
+ end
49
+
50
+ it 'takes as string the result of #to_s method' do
51
+ expect(subject).to eq('ltr')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDirection::Strategy do
4
+ it { expect(subject).to respond_to(:run).with(1).argument }
5
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDirection::StringMethods do
4
+ subject { 'abc' }
5
+
6
+ before :each do
7
+ String.send(:include, StringDirection::StringMethods)
8
+ end
9
+
10
+ describe '#direction' do
11
+ it 'returns string direction' do
12
+ expect(subject.direction).to eq(StringDirection::LTR)
13
+ end
14
+ end
15
+
16
+ describe '#ltr?' do
17
+ it 'returns whether string direction is left-to-right' do
18
+ expect(subject.ltr?).to eq(true)
19
+ end
20
+ end
21
+
22
+ describe '#rtl?' do
23
+ it 'returns whether string direction is right-to-left' do
24
+ expect(subject.rtl?).to eq(false)
25
+ end
26
+ end
27
+
28
+ describe '#bidi?' do
29
+ it 'returns whether string direction is bidirectional' do
30
+ expect(subject.bidi?).to eq(false)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDirection do
4
+ it 'has a version number' do
5
+ expect(described_class::VERSION).not_to be nil
6
+ end
7
+
8
+ describe '::LTR' do
9
+ it "is 'ltr'" do
10
+ expect(described_class::LTR).to eq('ltr')
11
+ end
12
+ end
13
+
14
+ describe '::RTL' do
15
+ it "is 'rtl'" do
16
+ expect(described_class::RTL).to eq('rtl')
17
+ end
18
+ end
19
+
20
+ describe '::BIDI' do
21
+ it "is 'bidi'" do
22
+ expect(described_class::BIDI).to eq('bidi')
23
+ end
24
+ end
25
+
26
+ describe '::configure' do
27
+ it 'initializes configuration instance var with an instance of StringDirection::Configuration' do
28
+ described_class.configure {}
29
+
30
+ expect(described_class.configuration).to be_an_instance_of(StringDirection::Configuration)
31
+ end
32
+
33
+ it 'yields the Configuration instance' do
34
+ expect { |b| described_class.configure(&b) }.to yield_with_args(StringDirection.configuration)
35
+ end
36
+ end
37
+
38
+ describe '#reset_configuration' do
39
+ it 'sets its configuration instance var as a new instance of Configuration' do
40
+ StringDirection.configure {}
41
+ configuration = StringDirection.configuration
42
+
43
+ StringDirection.reset_configuration
44
+
45
+ expect(StringDirection.configuration).to be_an_instance_of(StringDirection::Configuration)
46
+ expect(StringDirection.configuration).not_to eq(configuration)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'string-direction/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'string-direction'
8
+ s.version = StringDirection::VERSION
9
+ s.summary = 'Automatic detection of text direction (ltr, rtl or bidi) for strings'
10
+ s.description = 'https://github.com/waiting-for-dev/string-direction/'
11
+ s.license = 'GPL3'
12
+ s.homepage = 'https://github.com/waiting-for-dev/string-direction/'
13
+ s.authors = ['Marc Busqué']
14
+ s.email = 'marc@lamarciana.com'
15
+ s.files = `git ls-files`.split("\n")
16
+
17
+ s.add_runtime_dependency 'yard', '~>0.8'
18
+
19
+ s.add_development_dependency 'bundler', '~> 1.10'
20
+ s.add_development_dependency 'rake', '~> 10.4'
21
+ s.add_development_dependency 'rspec', '~> 3.3'
22
+ s.add_development_dependency 'pry', '~> 0.10'
23
+ s.add_development_dependency 'pry-byebug', '~> 3.2'
24
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: string-direction
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Busqué
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-18 00:00:00.000000000 Z
11
+ date: 2015-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard
@@ -25,33 +25,75 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
- name: redcarpet
28
+ name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.2'
34
- type: :runtime
33
+ version: '1.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.4'
48
+ type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '2.2'
54
+ version: '10.4'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '2.13'
61
+ version: '3.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.2'
48
90
  type: :development
49
91
  prerelease: false
50
92
  version_requirements: !ruby/object:Gem::Requirement
51
93
  requirements:
52
94
  - - "~>"
53
95
  - !ruby/object:Gem::Version
54
- version: '2.13'
96
+ version: '3.2'
55
97
  description: https://github.com/waiting-for-dev/string-direction/
56
98
  email: marc@lamarciana.com
57
99
  executables: []
@@ -61,12 +103,28 @@ files:
61
103
  - ".gitignore"
62
104
  - ".yardopts"
63
105
  - COPYING.txt
106
+ - Gemfile
64
107
  - README.md
108
+ - Rakefile
109
+ - bin/console
110
+ - bin/setup
65
111
  - lib/string-direction.rb
66
- - lib/string-direction/string-direction.rb
112
+ - lib/string-direction/configuration.rb
113
+ - lib/string-direction/detector.rb
114
+ - lib/string-direction/strategies/characters_strategy.rb
115
+ - lib/string-direction/strategies/marks_strategy.rb
116
+ - lib/string-direction/strategy.rb
117
+ - lib/string-direction/string_methods.rb
118
+ - lib/string-direction/version.rb
67
119
  - spec/spec_helper.rb
68
- - spec/string/string_spec.rb
69
- - string-direction.spec
120
+ - spec/string-direction/configuration_spec.rb
121
+ - spec/string-direction/detector_spec.rb
122
+ - spec/string-direction/strategies/characters_strategy_spec.rb
123
+ - spec/string-direction/strategies/marks_strategy_spec.rb
124
+ - spec/string-direction/strategy_spec.rb
125
+ - spec/string-direction/string_methods_spec.rb
126
+ - spec/string-direction_spec.rb
127
+ - string-direction.gemspec
70
128
  homepage: https://github.com/waiting-for-dev/string-direction/
71
129
  licenses:
72
130
  - GPL3
@@ -87,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
145
  version: '0'
88
146
  requirements: []
89
147
  rubyforge_project:
90
- rubygems_version: 2.2.2
148
+ rubygems_version: 2.4.5
91
149
  signing_key:
92
150
  specification_version: 4
93
151
  summary: Automatic detection of text direction (ltr, rtl or bidi) for strings
@@ -1,102 +0,0 @@
1
- #encoding: UTF-8
2
-
3
- # Module with all the logic for automatic detection of text direction. It will be included in String class.
4
- module StringDirection
5
- # left-to-right unicode mark
6
- LTR_MARK = "\u200e"
7
-
8
- # right-to-left unicode mark
9
- RTL_MARK = "\u200f"
10
-
11
- # returns the direction in which a string is written
12
- #
13
- # @return ["lft"] if it's a left-to-right string
14
- # @return ["rtl"] if it's a right-to-left string
15
- # @return ["bidi"] if it's a bi-directinal string
16
- def direction
17
- if has_ltr_mark? and has_rtl_mark?
18
- 'bidi'
19
- elsif has_ltr_mark?
20
- 'ltr'
21
- elsif has_rtl_mark?
22
- 'rtl'
23
- elsif not has_rtl_characters?
24
- 'ltr'
25
- elsif has_ltr_characters?
26
- 'bidi'
27
- else
28
- 'rtl'
29
- end
30
- end
31
-
32
- # whether string is a left-to-right one
33
- #
34
- # @return [Boolean] true if it is a left-to-right string, false otherwise
35
- def is_ltr?
36
- (direction == 'ltr') ? true : false
37
- end
38
-
39
- # whether string is a right-to-left one
40
- #
41
- # @return [Boolean] true if it is a right-to-left string, false otherwise
42
- def is_rtl?
43
- (direction == 'rtl') ? true : false
44
- end
45
-
46
- # whether string is a bi-directional one
47
- #
48
- # @return [Boolean] true if it is a bi-directional string, false otherwise
49
- def is_bidi?
50
- (direction == 'bidi') ? true : false
51
- end
52
-
53
- # returns whether string contains the unicode left-to-right mark
54
- #
55
- # @return [Boolean] true if it containts ltr mark, false otherwise
56
- def has_ltr_mark?
57
- match(/^(.*)#{LTR_MARK}(.*)$/) ? true : false
58
- end
59
-
60
- # returns whether string contains the unicode right-to-left mark
61
- #
62
- # @return [Boolean] true if it containts rtl mark, false otherwise
63
- def has_rtl_mark?
64
- match(/^(.*)#{RTL_MARK}(.*)$/) ? true : false
65
- end
66
-
67
- # returns whether string contains some right-to-left character
68
- #
69
- # @return [Boolean] true if it containts rtl characters, false otherwise
70
- def has_rtl_characters?
71
- match(/[#{StringDirection::join_scripts_for_regexp(StringDirection.rtl_scripts)}]/) ? true : false
72
- end
73
-
74
- # returns whether string contains some left-to-right character
75
- #
76
- # @return [Boolean] true if it containts ltr characters, false otherwise
77
- def has_ltr_characters?
78
- # ignore unicode marks, punctuations, symbols, separator and other general categories
79
- gsub(/[\p{M}\p{P}\p{S}\p{Z}\p{C}]/, '').match(/[^#{StringDirection::join_scripts_for_regexp(StringDirection.rtl_scripts)}]/) ? true : false
80
- end
81
-
82
- class << self
83
- attr_accessor :rtl_scripts
84
-
85
- # hook that is called when the module is included and that initializes rtl_scripts
86
- #
87
- # @param [Module] base The base module from within current module is included
88
- def included(base)
89
- @rtl_scripts = %w[Arabic Hebrew Nko Kharoshthi Phoenician Syriac Thaana Tifinagh]
90
- end
91
-
92
- # given an array of script names, which should be supported by Ruby {http://www.ruby-doc.org/core-1.9.3/Regexp.html#label-Character+Properties regular expression properties}, returns a string where all of them are concatenaded inside a "\\p{}" construction
93
- #
94
- # @param [Array] scripts the array of script names
95
- # @return [String] the script names joined ready to be used in the construction of a regular expression
96
- # @example
97
- # StringDirection.join_scripts_for_regexp(%w[Arabic Hebrew]) #=> "\p{Arabic}\p{Hebrew}"
98
- def join_scripts_for_regexp(scripts)
99
- scripts.map { |script| '\p{'+script+'}' }.join
100
- end
101
- end
102
- end
@@ -1,96 +0,0 @@
1
- #encoding: UTF-8
2
- require 'spec_helper'
3
-
4
- describe String do
5
- let(:english) { 'English' }
6
- let(:arabic) { 'العربية' }
7
- describe "#direction" do
8
- context "when marks are present" do
9
- it "should return 'ltr' if it contains the left-to-right mark and no right-to-left mark" do
10
- string = String::LTR_MARK+english
11
- string.direction.should eql 'ltr'
12
- end
13
- it "should return 'rtl' if it contains the right-to-left mark and no left-to-right mark" do
14
- string = String::RTL_MARK+arabic
15
- string.direction.should eql 'rtl'
16
- end
17
- it "should return 'bidi' if it contains both the left-to-right mark and the right-to-left mark" do
18
- string = String::LTR_MARK+english+String::RTL_MARK+arabic
19
- string.direction.should eql 'bidi'
20
- end
21
- end
22
- context "when marks are not present" do
23
- it "should return 'ltr' if no right-to-left character is present" do
24
- string = english
25
- string.direction.should eql 'ltr'
26
- end
27
- it "should return 'rtl' if only right-to-left character are present" do
28
- string = arabic
29
- string.direction.should eql 'rtl'
30
- end
31
- it "should return 'bidi' if both left-to-right and right-to-left characters are present" do
32
- string = arabic+' '+english
33
- string.direction.should eql 'bidi'
34
- end
35
- end
36
- context "when default rtl scripts are changed" do
37
- let(:new_rtl_script) { 'Latin' }
38
- let(:old_rtl_script) { 'Arabic' }
39
- it "should return 'rtl' if there are characters for an added right-to-left script and no marks characters are present" do
40
- StringDirection.rtl_scripts << new_rtl_script
41
- string = english
42
- string.direction.should eql 'rtl'
43
- end
44
- it "should return 'ltr' if there are characters for a deleted right-to-left script (so now ltr) and no mark characters are present" do
45
- StringDirection.rtl_scripts.delete old_rtl_script
46
- string = arabic
47
- string.direction.should eql 'ltr'
48
- end
49
- after :each do
50
- StringDirection.rtl_scripts.delete new_rtl_script if StringDirection.rtl_scripts.include? new_rtl_script
51
- StringDirection.rtl_scripts << old_rtl_script unless StringDirection.rtl_scripts.include? old_rtl_script
52
- end
53
- context "when special characters are present" do
54
- it "should ignore special characters for the direction detection" do
55
- mark = "\u0903"
56
- punctuation = "_"
57
- symbol = "€"
58
- separator = " "
59
- other = "\u0005"
60
- string = arabic+mark+punctuation+symbol+separator+other
61
- string.direction.should eql 'rtl'
62
- end
63
- end
64
- end
65
- end
66
- describe "#is_ltr?" do
67
- it "should return true if it is a left-to-right string" do
68
- string = english
69
- string.is_ltr?.should be_true
70
- end
71
- it "should return false if it is not a left-to-right string" do
72
- string = arabic
73
- string.is_ltr?.should be_false
74
- end
75
- end
76
- describe "#is_rtl?" do
77
- it "should return true if it is a right-to-left string" do
78
- string = arabic
79
- string.is_rtl?.should be_true
80
- end
81
- it "should return false if it is not a right-to-left string" do
82
- string = english
83
- string.is_rtl?.should be_false
84
- end
85
- end
86
- describe "#is_bidi?" do
87
- it "should return true if it is a bi-directional string" do
88
- string = english+' '+arabic
89
- string.is_bidi?.should be_true
90
- end
91
- it "should return false if it is not a bi-directional string" do
92
- string = english
93
- string.is_bidi?.should be_false
94
- end
95
- end
96
- end
@@ -1,16 +0,0 @@
1
- Gem::Specification.new do |s|
2
- s.name = 'string-direction'
3
- s.version = '0.0.4'
4
- s.summary = 'Automatic detection of text direction (ltr, rtl or bidi) for strings'
5
- s.description = 'https://github.com/waiting-for-dev/string-direction/'
6
- s.license = 'GPL3'
7
- s.homepage = 'https://github.com/waiting-for-dev/string-direction/'
8
- s.authors = ['Marc Busqué']
9
- s.email = 'marc@lamarciana.com'
10
- s.files = `git ls-files`.split("\n")
11
-
12
- s.add_runtime_dependency "yard", "~>0.8"
13
- s.add_runtime_dependency "redcarpet", "~>2.2"
14
-
15
- s.add_development_dependency "rspec", "~>2.13"
16
- end