string-direction 0.0.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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