string_formatter 1.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ class FormatEvaluator
2
+
3
+ ################
4
+ # #
5
+ # Declarations #
6
+ # #
7
+ ################
8
+
9
+ attr_reader :evaluations
10
+
11
+ ###############
12
+ # #
13
+ # Constructor #
14
+ # #
15
+ ###############
16
+
17
+ def initialize(evaluations)
18
+ @evaluations = evaluations
19
+ end
20
+
21
+ ####################
22
+ # #
23
+ # Instance Methods #
24
+ # #
25
+ ####################
26
+
27
+ def evaluate(object, parsed_components)
28
+ evaluated = parsed_components.map do |component|
29
+ evaluate_component(object, component)
30
+ end
31
+
32
+ evaluated.join ''
33
+ end
34
+
35
+ protected
36
+
37
+ def behavior_for(format)
38
+ format = format.to_s
39
+
40
+ evaluations.fetch(format) do
41
+ evaluations[format] = ->() { format }
42
+ end
43
+ end
44
+
45
+ def evaluate_component(object, component)
46
+ case component
47
+ when String
48
+ component
49
+ when Array
50
+ evaluate_format(object, *component)
51
+ else
52
+ raise ArgumentError, component.inspect
53
+ end
54
+ end
55
+
56
+ def evaluate_format(object, format, *options)
57
+ behavior = behavior_for(format)
58
+
59
+ object.instance_exec(*options, &behavior)
60
+ end
61
+
62
+ end
@@ -0,0 +1,91 @@
1
+ class FormatParser
2
+
3
+ ################
4
+ # #
5
+ # Declarations #
6
+ # #
7
+ ################
8
+
9
+ attr_reader :escape_sequences, :escape_options
10
+
11
+ ###############
12
+ # #
13
+ # Constructor #
14
+ # #
15
+ ###############
16
+
17
+ def initialize(escape_sequences, escape_options)
18
+ @escape_sequences = escape_sequences
19
+ @escape_options = escape_options
20
+ end
21
+
22
+ ####################
23
+ # #
24
+ # Instance Methods #
25
+ # #
26
+ ####################
27
+
28
+ def parse(string)
29
+ split_string = string.scan(string_format_regex)
30
+
31
+ split_string.map { |match| extract_escape(match) }
32
+ end
33
+
34
+ protected
35
+
36
+ def escape_regexp(escape)
37
+ escape_option = escape_options[escape]
38
+ clean_escape = Regexp.escape(escape)
39
+
40
+ escape_option.to_s + clean_escape
41
+ end
42
+
43
+ def escape_regex_fragment
44
+ fragments = sorted_sequences.map { |escape| escape_regexp(escape) }
45
+ fragments << '.'
46
+ fragments.join '|'
47
+ end
48
+
49
+ def explicit_sequences
50
+ escape_sequences.select do |escape|
51
+ escape.length > 1 || escape_options[escape]
52
+ end
53
+ end
54
+
55
+ def extract_escape(match)
56
+ if match[0]
57
+ match[0]
58
+ else
59
+ extract_options match[1]
60
+ end
61
+ end
62
+
63
+ def extract_options(match)
64
+ return [match] if match.length == 1
65
+
66
+ matching_escape = explicit_sequences.detect do |escape|
67
+ match =~ /#{Regexp.escape(escape)}$/
68
+ end
69
+
70
+ if match == matching_escape
71
+ [match]
72
+ else
73
+ options = match[0...-matching_escape.length]
74
+
75
+ [matching_escape, options]
76
+ end
77
+ end
78
+
79
+ def multichar_sequences
80
+ escape_sequences.select { |seq| seq.length > 1 }
81
+ end
82
+
83
+ def sorted_sequences
84
+ explicit_sequences.sort_by(&:length).reverse
85
+ end
86
+
87
+ def string_format_regex
88
+ /([^%]+)|%(#{ escape_regex_fragment })/
89
+ end
90
+
91
+ end
@@ -0,0 +1,59 @@
1
+ module Formattable
2
+
3
+ module ClassMethods
4
+
5
+ def define_format_string(meth, options = {}, &block)
6
+ base_class = options.fetch(:with) { StringFormatter }
7
+ klass = formatter_class(base_class, block)
8
+
9
+ set_formatter_for meth, klass
10
+
11
+ define_formatter_method(meth)
12
+
13
+ define_default_alias(meth) if options[:default]
14
+ end
15
+
16
+ def formatter_for(meth)
17
+ @formatters ||= {}
18
+ @formatters[meth]
19
+ end
20
+
21
+ protected
22
+
23
+ def define_default_alias(meth)
24
+ alias_method :%, meth
25
+ end
26
+
27
+ def define_formatter_method(meth)
28
+ define_method(meth) do |format_string|
29
+ formatter = self.class.formatter_for meth
30
+ formatter.format(self, format_string)
31
+ end
32
+ end
33
+
34
+ def formatter_class(superclass, inline_definition)
35
+ if inline_definition
36
+ Class.new(superclass, &inline_definition)
37
+ else
38
+ superclass
39
+ end
40
+ end
41
+
42
+ def set_formatter_for(meth, formatter_class)
43
+ @formatters ||= {}
44
+ @formatters[meth] = formatter_class.new
45
+ end
46
+
47
+ end
48
+
49
+ ##################
50
+ # #
51
+ # Module Methods #
52
+ # #
53
+ ##################
54
+
55
+ def self.included(base)
56
+ base.send :extend, ClassMethods
57
+ end
58
+
59
+ end
@@ -1,76 +1,64 @@
1
+ require './lib/formattable'
2
+ require './lib/format_parser'
3
+ require './lib/format_evaluator'
4
+
1
5
  class StringFormatter
2
6
 
3
- ################
4
- # #
5
- # Declarations #
6
- # #
7
- ################
8
-
9
- VERSION = "1.0.0"
10
-
11
7
  #############
12
8
  # #
13
9
  # Constants #
14
10
  # #
15
11
  #############
16
12
 
17
- STRING_FORMAT_REGEX = /[^%]+|%./
18
-
19
- SPECIAL_CHARACTER_FORMATS = {
20
- 'ampersand' => '&',
21
- 'at' => '@',
22
- 'backslash' => '\\',
23
- 'backtick' => '`',
24
- 'bang' => '!',
25
- 'caret' => '^',
26
- 'cash' => '$',
27
- 'colon' => ':',
28
- 'comma' => ',',
29
- 'dash' => '-',
30
- 'double_quote' => '"',
31
- 'eight' => '8',
32
- 'equals' => '=',
33
- 'five' => '5',
34
- 'four' => '4',
35
- 'hash' => '#',
36
- 'left_bracket' => '[',
37
- 'left_chevron' => '<',
38
- 'left_paren' => '(',
39
- 'left_stache' => '{',
40
- 'nine' => '9',
41
- 'one' => '1',
42
- 'percent' => '%', # Defining this method is not recommended
43
- 'period' => '.',
44
- 'pipe' => '|',
45
- 'plus' => '+',
46
- 'quote' => "'",
13
+ PUNCTUATION_FORMATS = {
14
+ 'ampersand' => '&',
15
+ 'at' => '@',
16
+ 'backslash' => '\\',
17
+ 'backtick' => '`',
18
+ 'bang' => '!',
19
+ 'caret' => '^',
20
+ 'cash' => '$',
21
+ 'colon' => ':',
22
+ 'comma' => ',',
23
+ 'dash' => '-',
24
+ 'double_quote' => '"',
25
+ 'eight' => '8',
26
+ 'equals' => '=',
27
+ 'five' => '5',
28
+ 'four' => '4',
29
+ 'hash' => '#',
30
+ 'left_brace' => '{',
31
+ 'left_bracket' => '[',
32
+ 'left_chevron' => '<',
33
+ 'left_paren' => '(',
34
+ 'left_shovel' => '<<',
35
+ 'left_stache' => '{{',
36
+ 'nine' => '9',
37
+ 'one' => '1',
38
+ # Defining this method is not recommended:
39
+ 'percent' => '%',
40
+ 'period' => '.',
41
+ 'pipe' => '|',
42
+ 'plus' => '+',
43
+ 'quote' => "'",
44
+ 'right_brace' => '}',
47
45
  'right_bracket' => ']',
48
46
  'right_chevron' => '>',
49
- 'right_paren' => ')',
50
- 'right_stache' => '}',
51
- 'semicolon' => ';',
52
- 'seven' => '7',
53
- 'six' => '6',
54
- 'slash' => '/',
55
- 'splat' => '*',
56
- 'three' => '3',
57
- 'tilde' => '~',
58
- 'two' => '2',
59
- 'underscore' => '_',
60
- 'what' => '?',
61
- 'zero' => '0'
62
- }
63
-
64
- ###############
65
- # #
66
- # Constructor #
67
- # #
68
- ###############
69
-
70
- def initialize(object, format_string)
71
- @object = object
72
- @format_string = format_string
73
- end
47
+ 'right_paren' => ')',
48
+ 'right_shovel' => '>>',
49
+ 'right_stache' => '}}',
50
+ 'semicolon' => ';',
51
+ 'seven' => '7',
52
+ 'six' => '6',
53
+ 'slash' => '/',
54
+ 'splat' => '*',
55
+ 'three' => '3',
56
+ 'tilde' => '~',
57
+ 'two' => '2',
58
+ 'underscore' => '_',
59
+ 'what' => '?',
60
+ 'zero' => '0'
61
+ }.freeze
74
62
 
75
63
  #################
76
64
  # #
@@ -78,55 +66,90 @@ class StringFormatter
78
66
  # #
79
67
  #################
80
68
 
81
- def self.escape_character(object, char)
82
- escape_method = @escapes[char]
83
- escape_method ? escape_method[object] : char
69
+ def self.method_missing(escape_sequence, *args, &behavior)
70
+ super unless behavior
71
+
72
+ if escaping_as_punctuation? && valid_punctuation_sequence?(escape_sequence)
73
+ escape_sequence = characters_for_punctuation_sequence(escape_sequence)
74
+ end
75
+
76
+ set_escape(escape_sequence, behavior)
77
+ set_escape_options(escape_sequence, args.first) if args.length > 0
84
78
  end
85
-
86
- def self.method_missing(meth, *args, &block)
87
- str = meth.to_s
88
- if str.length == 1
89
- @escapes ||= {}
90
- @escapes[str] = block
91
- elsif SPECIAL_CHARACTER_FORMATS[str]
79
+
80
+ def self.punctuation
81
+ @parsing_punctuation = true
82
+ end
83
+
84
+ protected
85
+
86
+ def self.characters_for_punctuation_sequence(sequence)
87
+ PUNCTUATION_FORMATS.fetch(sequence.to_s)
88
+ end
89
+
90
+ def self.escapes
92
91
  @escapes ||= {}
93
- @escapes[ SPECIAL_CHARACTER_FORMATS[str] ] = block
94
- else
95
- super
96
92
  end
97
- end
93
+
94
+ def self.escape_options
95
+ @escape_options ||= {}
96
+ end
97
+
98
+ def self.escaping_as_punctuation?
99
+ @parsing_punctuation
100
+ end
101
+
102
+ def self.set_escape(escape_sequence, behavior)
103
+ escapes[escape_sequence.to_s] = behavior
104
+ end
105
+
106
+ def self.set_escape_options(escape_sequence, options_regex)
107
+ escape_options[escape_sequence.to_s] = options_regex
108
+ end
109
+
110
+ def self.valid_punctuation_sequence?(sequence)
111
+ PUNCTUATION_FORMATS.include? sequence.to_s
112
+ end
113
+
114
+ public
98
115
 
99
116
  ####################
100
117
  # #
101
118
  # Instance Methods #
102
119
  # #
103
120
  ####################
104
-
105
- def to_s
106
- split_string = @format_string.scan STRING_FORMAT_REGEX
107
-
108
- split_string.map { |str| translate_component(str) }.join
121
+
122
+ def escape(object, format_string)
123
+ escape_method = self.class.escapes[format_string]
124
+ escape_method ? escape_method[object] : format_string
109
125
  end
110
126
 
111
- def translate_component(string)
112
- if string[0...1] == '%'
113
- char = string[1..-1]
114
- self.class.escape_character(@object, char)
115
- else
116
- string
117
- end
127
+ def format(object, format_string)
128
+ parsed_string = parser.parse(format_string)
129
+
130
+ evaluator.evaluate(object, parsed_string)
118
131
  end
119
-
120
- end
121
132
 
122
- class Module
123
-
124
- def define_format_string(formatter_method, options = {})
125
- formatter_class = options[:with]
133
+ protected
126
134
 
127
- define_method formatter_method do |format_string|
128
- formatter_class.new(self, format_string).to_s
135
+ def escape_options
136
+ self.class.escape_options
129
137
  end
130
- end
131
-
138
+
139
+ def escape_sequences
140
+ self.class.escapes.keys
141
+ end
142
+
143
+ def evaluations
144
+ self.class.escapes
145
+ end
146
+
147
+ def evaluator
148
+ @evaluator ||= FormatEvaluator.new(evaluations)
149
+ end
150
+
151
+ def parser
152
+ @parser ||= FormatParser.new(escape_sequences, escape_options)
153
+ end
154
+
132
155
  end
metadata CHANGED
@@ -1,92 +1,50 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: string_formatter
3
- version: !ruby/object:Gem::Version
4
- hash: 23
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.1
5
5
  prerelease:
6
- segments:
7
- - 1
8
- - 0
9
- - 0
10
- version: 1.0.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Mark Josef
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-01-11 00:00:00 -05:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: hoe
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 47
30
- segments:
31
- - 2
32
- - 8
33
- - 0
34
- version: 2.8.0
35
- type: :development
36
- version_requirements: *id001
37
- description: A gem for creating strf-style methods in any Ruby object.
38
- email:
39
- - McPhage@gmail.com
12
+ date: 2013-07-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! " Ruby has useful formatter methods, like Time#strftime. This gem\n
15
+ \ allows you to define your own similar methods on any Ruby\n object easily,
16
+ using a nice little DSL.\n"
17
+ email: McPhage@gmail.com
40
18
  executables: []
41
-
42
19
  extensions: []
43
-
44
- extra_rdoc_files:
45
- - History.txt
46
- - Manifest.txt
47
- - README.txt
48
- files:
49
- - .autotest
50
- - History.txt
51
- - Manifest.txt
52
- - README.txt
53
- - Rakefile
20
+ extra_rdoc_files: []
21
+ files:
54
22
  - lib/string_formatter.rb
55
- - test/test_string_formatter.rb
56
- has_rdoc: true
23
+ - lib/format_evaluator.rb
24
+ - lib/format_parser.rb
25
+ - lib/formattable.rb
57
26
  homepage: http://github.com/mark/string_formatter
58
27
  licenses: []
59
-
60
28
  post_install_message:
61
- rdoc_options:
62
- - --main
63
- - README.txt
64
- require_paths:
29
+ rdoc_options: []
30
+ require_paths:
65
31
  - lib
66
- required_ruby_version: !ruby/object:Gem::Requirement
32
+ required_ruby_version: !ruby/object:Gem::Requirement
67
33
  none: false
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- hash: 3
72
- segments:
73
- - 0
74
- version: "0"
75
- required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
39
  none: false
77
- requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- hash: 3
81
- segments:
82
- - 0
83
- version: "0"
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
84
44
  requirements: []
85
-
86
- rubyforge_project: string_formatter
87
- rubygems_version: 1.4.2
45
+ rubyforge_project:
46
+ rubygems_version: 1.8.10
88
47
  signing_key:
89
48
  specification_version: 3
90
- summary: A gem for creating strf-style methods in any Ruby object.
91
- test_files:
92
- - test/test_string_formatter.rb
49
+ summary: A gem for creating strf-style methods on any Ruby object.
50
+ test_files: []
data/.autotest DELETED
@@ -1,23 +0,0 @@
1
- # -*- ruby -*-
2
-
3
- require 'autotest/restart'
4
-
5
- # Autotest.add_hook :initialize do |at|
6
- # at.extra_files << "../some/external/dependency.rb"
7
- #
8
- # at.libs << ":../some/external"
9
- #
10
- # at.add_exception 'vendor'
11
- #
12
- # at.add_mapping(/dependency.rb/) do |f, _|
13
- # at.files_matching(/test_.*rb$/)
14
- # end
15
- #
16
- # %w(TestA TestB).each do |klass|
17
- # at.extra_class_map[klass] = "test/test_misc.rb"
18
- # end
19
- # end
20
-
21
- # Autotest.add_hook :run_command do |at|
22
- # system "rake build"
23
- # end
data/History.txt DELETED
@@ -1,3 +0,0 @@
1
- === 1.0.0 / 2011-01-11
2
-
3
- * First (test) release! Yay!
data/Manifest.txt DELETED
@@ -1,7 +0,0 @@
1
- .autotest
2
- History.txt
3
- Manifest.txt
4
- README.txt
5
- Rakefile
6
- lib/string_formatter.rb
7
- test/test_string_formatter.rb
data/README.txt DELETED
@@ -1,92 +0,0 @@
1
- = string_formatter
2
-
3
- * http://github.com/mark/string_formatter
4
-
5
- == DESCRIPTION:
6
-
7
- A gem for creating strf-style methods in any Ruby object.
8
-
9
- == FEATURES/PROBLEMS:
10
-
11
- * Creates strf-style methods
12
-
13
- == SYNOPSIS:
14
-
15
- class PersonFormatter < StringFormatter
16
-
17
- f { |p| p.first_name }
18
- F { |p| p.first_name.upcase }
19
- l { |p| p.last_name }
20
- pipe { |p| 'PIPE' }
21
-
22
- end
23
-
24
- class UpcaseFormatter < StringFormatter
25
-
26
- f { |p| p.first_name.upcase }
27
- l { |p| p.last_name.upcase }
28
-
29
- end
30
-
31
- class Person
32
- attr_accessor :first_name, :last_name
33
-
34
- define_format_string :strf, :with => PersonFormatter
35
- define_format_string :strfup, :with => UpcaseFormatter
36
-
37
- def initialize(*names)
38
- @first_name, @last_name = names
39
- end
40
-
41
- end
42
-
43
- p = Person.new("Bob", "Smith")
44
-
45
- p.strf('%l, %f %|')
46
- # => "Smith, Bob PIPE"
47
-
48
- p.strfup('%l, %f')
49
- # => "SMITH, BOB"
50
-
51
-
52
- == REQUIREMENTS:
53
-
54
- * None
55
-
56
- == INSTALL:
57
-
58
- * gem install string_formatter
59
-
60
- == DEVELOPERS:
61
-
62
- After checking out the source, run:
63
-
64
- $ rake newb
65
-
66
- This task will install any missing dependencies, run the tests/specs,
67
- and generate the RDoc.
68
-
69
- == LICENSE:
70
-
71
- (The MIT License)
72
-
73
- Copyright (c) 2011 Mark Josef
74
-
75
- Permission is hereby granted, free of charge, to any person obtaining
76
- a copy of this software and associated documentation files (the
77
- 'Software'), to deal in the Software without restriction, including
78
- without limitation the rights to use, copy, modify, merge, publish,
79
- distribute, sublicense, and/or sell copies of the Software, and to
80
- permit persons to whom the Software is furnished to do so, subject to
81
- the following conditions:
82
-
83
- The above copyright notice and this permission notice shall be
84
- included in all copies or substantial portions of the Software.
85
-
86
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile DELETED
@@ -1,21 +0,0 @@
1
- # -*- ruby -*-
2
-
3
- require 'rubygems'
4
- require 'hoe'
5
-
6
- # Hoe.plugin :compiler
7
- # Hoe.plugin :gem_prelude_sucks
8
- # Hoe.plugin :inline
9
- # Hoe.plugin :racc
10
- # Hoe.plugin :rubyforge
11
-
12
- Hoe.spec 'string_formatter' do
13
- # HEY! If you fill these out in ~/.hoe_template/Rakefile.erb then
14
- # you'll never have to touch them again!
15
- # (delete this comment too, of course)
16
-
17
- developer('Mark Josef', 'McPhage@gmail.com')
18
-
19
- end
20
-
21
- # vim: syntax=ruby
@@ -1,8 +0,0 @@
1
- require "test/unit"
2
- require "string_formatter"
3
-
4
- class TestStringFormatter < Test::Unit::TestCase
5
- def test_sanity
6
- flunk "write tests or I will kneecap you"
7
- end
8
- end