i18nliner 0.0.1 → 0.0.2

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.
Files changed (37) hide show
  1. data/README.md +151 -104
  2. data/lib/i18nliner/base.rb +47 -0
  3. data/lib/i18nliner/call_helpers.rb +39 -13
  4. data/lib/i18nliner/commands/check.rb +20 -10
  5. data/lib/i18nliner/commands/dump.rb +17 -0
  6. data/lib/i18nliner/commands/generic_command.rb +10 -1
  7. data/lib/i18nliner/errors.rb +11 -1
  8. data/lib/i18nliner/erubis.rb +12 -0
  9. data/lib/i18nliner/extensions/controller.rb +25 -0
  10. data/lib/i18nliner/extensions/core.rb +61 -0
  11. data/lib/i18nliner/extensions/inferpolation.rb +28 -0
  12. data/lib/i18nliner/extensions/model.rb +29 -0
  13. data/lib/i18nliner/extensions/view.rb +28 -0
  14. data/lib/i18nliner/extractors/ruby_extractor.rb +15 -33
  15. data/lib/i18nliner/extractors/sexp_helper.rb +38 -0
  16. data/lib/i18nliner/extractors/translate_call.rb +9 -11
  17. data/lib/i18nliner/extractors/translation_hash.rb +6 -6
  18. data/lib/i18nliner/pre_processors/erb_pre_processor.rb +334 -0
  19. data/lib/i18nliner/processors/abstract_processor.rb +26 -5
  20. data/lib/i18nliner/processors/erb_processor.rb +19 -3
  21. data/lib/i18nliner/processors/ruby_processor.rb +16 -5
  22. data/lib/i18nliner/railtie.rb +27 -0
  23. data/lib/i18nliner/scope.rb +4 -0
  24. data/lib/i18nliner.rb +5 -29
  25. data/lib/tasks/i18nliner.rake +10 -4
  26. data/spec/commands/check_spec.rb +22 -0
  27. data/spec/commands/dump_spec.rb +27 -0
  28. data/spec/extensions/core_spec.rb +71 -0
  29. data/spec/extensions/inferpolation_spec.rb +41 -0
  30. data/spec/extensions/view_spec.rb +42 -0
  31. data/spec/extractors/ruby_extractor_spec.rb +4 -5
  32. data/spec/extractors/translate_call_spec.rb +29 -2
  33. data/spec/fixtures/app/models/invalid.rb +5 -0
  34. data/spec/fixtures/app/models/valid.rb +5 -0
  35. data/spec/pre_processors/erb_pre_processor_spec.rb +194 -0
  36. data/spec/processors/erb_processor_spec.rb +28 -0
  37. metadata +88 -5
@@ -6,7 +6,8 @@ module I18nliner
6
6
  end
7
7
 
8
8
  def to_s
9
- error = self.class.name.humanize.sub(/ error\z/, '')
9
+ error = self.class.name.underscore.humanize
10
+ error.gsub!(/\AI18nliner\/| error\z/, '')
10
11
  error = "#{error} on line #{@line}"
11
12
  @detail ?
12
13
  error + " (got #{@detail.inspect})" :
@@ -14,6 +15,7 @@ module I18nliner
14
15
  end
15
16
  end
16
17
 
18
+ # extraction errors
17
19
  class InvalidSignatureError < ExtractionError; end
18
20
  class MissingDefaultError < ExtractionError; end
19
21
  class AmbiguousKeyError < ExtractionError; end
@@ -26,4 +28,12 @@ module I18nliner
26
28
  class InvalidOptionKeyError < ExtractionError; end
27
29
  class KeyAsScopeError < ExtractionError; end
28
30
  class KeyInUseError < ExtractionError; end
31
+
32
+ # pre-processing errors
33
+ class TBlockNestingError < StandardError; end
34
+ class MalformedErbError < StandardError; end
35
+ class UnwrappableContentError < StandardError; end
36
+
37
+ # runtime errors
38
+ class InvalidBlockUsageError < ArgumentError; end
29
39
  end
@@ -0,0 +1,12 @@
1
+ require 'action_view/template/handlers/erb'
2
+ require 'i18nliner/pre_processors/erb_pre_processor'
3
+
4
+ module I18nliner
5
+ class Erubis < ::ActionView::Template::Handlers::Erubis
6
+ def initialize(source, options = {})
7
+ source = I18nliner::PreProcessors::ErbPreProcessor.new(source).result
8
+ super(source, options)
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,25 @@
1
+ require 'i18nliner/base'
2
+ require 'i18nliner/call_helpers'
3
+ require 'i18nliner/extensions/inferpolation'
4
+
5
+ module I18nliner
6
+ module Extensions
7
+ module Controller
8
+ include Inferpolation
9
+
10
+ PATH = "app/controllers"
11
+ ALLOW_RELATIVE = Rails.version >= '4'
12
+
13
+ def translate(*args)
14
+ key, options = CallHelpers.infer_arguments(args)
15
+ options = inferpolate(options) if I18nliner.infer_interpolation_values
16
+ super(key, options)
17
+ end
18
+ alias :t :translate
19
+ alias :t! :translate
20
+ alias :translate! :translate
21
+ end
22
+ end
23
+ end
24
+
25
+
@@ -0,0 +1,61 @@
1
+ require 'i18nliner/scope'
2
+ require 'i18nliner/call_helpers'
3
+ require 'active_support/core_ext/string/output_safety'
4
+
5
+ module I18nliner
6
+ module Extensions
7
+ module Core
8
+ def translate(*args)
9
+ key, options = *CallHelpers.infer_arguments(args)
10
+ wrappers = options.delete(:wrappers)
11
+ result = super(key, options)
12
+ if wrappers
13
+ result = apply_wrappers(result, wrappers)
14
+ end
15
+ result
16
+ end
17
+ alias :t :translate
18
+ alias :t! :translate
19
+ alias :translate! :translate
20
+
21
+ # can't super this one yet :-/
22
+ def interpolate_hash_with_html_safety(string, values)
23
+ if string.html_safe? || values.values.any?(&:html_safe?)
24
+ string = ERB::Util.h(string) unless string.html_safe?
25
+ values.each do |key, value|
26
+ values[key] = ERB::Util.h(value) unless value.html_safe?
27
+ end
28
+ interpolate_hash_without_html_safety(string.to_str, values).html_safe
29
+ else
30
+ interpolate_hash_without_html_safety(string, values)
31
+ end
32
+ end
33
+
34
+ def self.extended(base)
35
+ base.instance_eval do
36
+ alias :interpolate_hash_without_html_safety :interpolate_hash
37
+ alias :interpolate_hash :interpolate_hash_with_html_safety
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def apply_wrappers(string, wrappers)
44
+ string = string.html_safe? ? string.dup : ERB::Util.h(string)
45
+ if wrappers.is_a?(Array)
46
+ wrappers = Hash[wrappers.each_with_index.map{ |w, i| ['*' * (1 + i), w]}]
47
+ end
48
+ wrappers.sort_by{ |k, v| -k.length }.each do |k, v|
49
+ pattern = pattern_for(k)
50
+ string.gsub!(pattern, v)
51
+ end
52
+ string.html_safe
53
+ end
54
+
55
+ def pattern_for(key)
56
+ escaped_key = Regexp.escape(key)
57
+ /#{escaped_key}(.*?)#{escaped_key}/
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,28 @@
1
+ module I18nliner
2
+ module Extensions
3
+ module Inferpolation
4
+ def inferpolate(options)
5
+ default = options[:default]
6
+ return options unless default
7
+
8
+ default.gsub!(/%\{((@)?\w+(.\w+)*)\}/).each do
9
+ match = $~
10
+ key = $1
11
+ ivar = $2
12
+ next match if options[key] || options[key.to_sym]
13
+ parts = key.split('.')
14
+ receiver = ivar ? instance_variable_get(parts.shift) : self
15
+ value = parts.inject(receiver) do |obj, message|
16
+ obj.respond_to?(message) ? obj.send(message) : nil
17
+ end
18
+
19
+ next match if value.nil?
20
+ new_key = key.sub(/\@/, '').gsub('.', '_')
21
+ options[new_key.to_sym] = value
22
+ "%{#{new_key}}"
23
+ end
24
+ options
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ require 'i18nliner/base'
2
+ require 'i18nliner/call_helpers'
3
+ require 'i18nliner/extensions/inferpolation'
4
+
5
+ module I18nliner
6
+ module Extensions
7
+ module Model
8
+ include Inferpolation
9
+
10
+ PATH = "app/models"
11
+ ALLOW_RELATIVE = false
12
+
13
+ def translate(*args)
14
+ key, options = CallHelpers.infer_arguments(args)
15
+ options = inferpolate(options) if I18nliner.infer_interpolation_values
16
+ I18n.t(key, options)
17
+ end
18
+ alias :t :translate
19
+ alias :t! :translate
20
+ alias :translate! :translate
21
+
22
+ def localize(*args)
23
+ I18n.localize(*args)
24
+ end
25
+ alias :l :localize
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,28 @@
1
+ require 'i18nliner/base'
2
+ require 'i18nliner/call_helpers'
3
+ require 'i18nliner/extensions/inferpolation'
4
+
5
+ module I18nliner
6
+ module Extensions
7
+ module View
8
+ include Inferpolation
9
+
10
+ PATH = "app/views"
11
+ ALLOW_RELATIVE = true
12
+
13
+ def translate(*args)
14
+ # if a block gets through to here, it either means:
15
+ # 1. the user did something weird (e.g. <%= t{ "haha" } %>)
16
+ # 2. the erb pre processor missed it somehow (bug)
17
+ raise InvalidBlockUsageError.new("block translate calls need to be output (i.e. `<%=`) and the block body must be of the form `%>your string<%`") if block_given?
18
+ raise ArgumentError.new("wrong number of arguments (0 for 1..3)") if args.empty? || args.size > 3
19
+ key, options = CallHelpers.infer_arguments(args)
20
+ options = inferpolate(options) if I18nliner.infer_interpolation_values
21
+ super(key, options)
22
+ end
23
+ alias :t :translate
24
+ alias :t! :translate
25
+ alias :translate! :translate
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,13 @@
1
+ require 'sexp_processor'
2
+ require 'i18nliner/errors'
3
+ require 'i18nliner/extractors/translate_call'
4
+ require 'i18nliner/extractors/sexp_helper'
5
+
1
6
  module I18nliner
2
7
  module Extractors
3
- class RubyExtractor < SexpProcessor
8
+ class RubyExtractor < ::SexpProcessor
9
+ include SexpHelper
10
+
4
11
  TRANSLATE_CALLS = [:t, :translate]
5
12
  attr_reader :current_line
6
13
 
@@ -20,7 +27,7 @@ module I18nliner
20
27
  receiver = process(exp.shift)
21
28
  receiver = receiver.last if receiver
22
29
  method = exp.shift
23
-
30
+
24
31
  if extractable_call?(receiver, method)
25
32
  @current_line = exp.line
26
33
 
@@ -33,7 +40,7 @@ module I18nliner
33
40
  # one
34
41
  process exp.shift until exp.empty?
35
42
  end
36
-
43
+
37
44
  s
38
45
  end
39
46
 
@@ -44,7 +51,8 @@ module I18nliner
44
51
  end
45
52
 
46
53
  def process_translate_call(receiver, method, args)
47
- call = TranslateCall.new(@scope, @current_line, receiver, method, args)
54
+ scope = receiver ? Scope.root : @scope
55
+ call = TranslateCall.new(@scope, @current_line, method, args)
48
56
  call.translations.each &@block
49
57
  end
50
58
 
@@ -57,38 +65,14 @@ module I18nliner
57
65
  end
58
66
  values
59
67
  end
60
-
68
+
61
69
  def evaluate_expression(exp)
62
- if exp.sexp_type == :lit || exp.sexp_type == :str
63
- exp.shift
64
- return exp.shift
65
- end
66
- return string_from(exp) if string_concatenation?(exp)
70
+ return raw(exp) if exp.sexp_type == :lit
71
+ return string_from(exp) if stringish?(exp)
67
72
  return hash_from(exp) if exp.sexp_type == :hash
68
73
  process(exp)
69
74
  UnsupportedExpression
70
75
  end
71
-
72
- def string_concatenation?(exp)
73
- exp.sexp_type == :call &&
74
- exp[2] == :+ &&
75
- exp.last &&
76
- exp.last.sexp_type == :str
77
- end
78
-
79
- def string_from(exp)
80
- exp.shift
81
- lhs = exp.shift
82
- exp.shift
83
- rhs = exp.shift
84
- if lhs.sexp_type == :str
85
- lhs.last + rhs.last
86
- elsif string_concatenation?(lhs)
87
- string_from(lhs) + rhs.last
88
- else
89
- return UnsupportedExpression
90
- end
91
- end
92
76
 
93
77
  def hash_from(exp)
94
78
  exp.shift
@@ -96,7 +80,5 @@ module I18nliner
96
80
  Hash[*values]
97
81
  end
98
82
  end
99
-
100
- class UnsupportedExpression; end
101
83
  end
102
84
  end
@@ -0,0 +1,38 @@
1
+ module I18nliner
2
+ module Extractors
3
+ module SexpHelper
4
+ def stringish?(exp)
5
+ exp && (exp.sexp_type == :str || string_concatenation?(exp))
6
+ end
7
+
8
+ def string_concatenation?(exp)
9
+ exp.sexp_type == :call &&
10
+ exp[2] == :+ &&
11
+ exp.last &&
12
+ exp.last.sexp_type == :str
13
+ end
14
+
15
+ def raw(exp)
16
+ exp.shift
17
+ return exp.shift
18
+ end
19
+
20
+ def string_from(exp)
21
+ return raw(exp) if exp.sexp_type == :str
22
+ exp.shift
23
+ lhs = exp.shift
24
+ exp.shift
25
+ rhs = exp.shift
26
+ if lhs.sexp_type == :str
27
+ lhs.last + rhs.last
28
+ elsif stringish?(lhs)
29
+ string_from(lhs) + rhs.last
30
+ else
31
+ UnsupportedExpression
32
+ end
33
+ end
34
+
35
+ class UnsupportedExpression; end
36
+ end
37
+ end
38
+ end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require 'i18nliner/base'
1
3
  require 'i18nliner/call_helpers'
2
4
  require 'i18nliner/errors'
3
5
 
@@ -6,10 +8,11 @@ module I18nliner
6
8
  class TranslateCall
7
9
  include CallHelpers
8
10
 
9
- def initialize(scope, line, receiver, method, args)
11
+ attr_reader :key, :default
12
+
13
+ def initialize(scope, line, method, args)
10
14
  @scope = scope
11
15
  @line = line
12
- @receiver = receiver
13
16
  @method = method
14
17
 
15
18
  normalize_arguments(args)
@@ -25,7 +28,7 @@ module I18nliner
25
28
  end
26
29
 
27
30
  def normalize
28
- @key = normalize_key(@key, @scope, @receiver)
31
+ @key = normalize_key(@key, @scope)
29
32
  @default = normalize_default(@default, @options || {})
30
33
  end
31
34
 
@@ -75,18 +78,13 @@ module I18nliner
75
78
  def normalize_arguments(args)
76
79
  raise InvalidSignatureError.new(@line, args) if args.empty?
77
80
 
78
- has_key = key_provided?(@scope, @receiver, *args)
79
- args.unshift infer_key(args[0]) if !has_key && args[0].is_a?(String) || args[0].is_a?(Hash)
80
-
81
- # [key, options] -> [key, nil, options]
82
- args.insert(1, nil) if has_key && args[1].is_a?(Hash) && args[2].nil?
83
-
84
- @key, @default, @options, *others = args
81
+ @key, @options, *others = infer_arguments(args)
85
82
 
86
83
  raise InvalidSignatureError.new(@line, args) if !others.empty?
87
84
  raise InvalidSignatureError.new(@line, args) unless @key.is_a?(Symbol) || @key.is_a?(String)
88
- raise InvalidSignatureError.new(@line, args) unless @default.nil? || @default.is_a?(String) || @default.is_a?(Hash)
89
85
  raise InvalidSignatureError.new(@line, args) unless @options.nil? || @options.is_a?(Hash)
86
+ @default = @options.delete(:default) if @options
87
+ raise InvalidSignatureError.new(@line, args) unless @default.nil? || @default.is_a?(String) || @default.is_a?(Hash)
90
88
  end
91
89
 
92
90
  def validate_interpolation_values(key, default)
@@ -1,10 +1,10 @@
1
- module I18nLine
1
+ module I18nliner
2
2
  module Extractors
3
3
  class TranslationHash < Hash
4
4
  attr_accessor :line
5
5
 
6
- def self.new(hash)
7
- hash.is_a?(self) ? hash : super
6
+ def self.new(hash = {})
7
+ hash.is_a?(self) ? hash : super().replace(hash)
8
8
  end
9
9
 
10
10
  def initialize(*args)
@@ -23,7 +23,7 @@ module I18nLine
23
23
  raise KeyAsScopeError, intermediate_key
24
24
  end
25
25
  else
26
- hash[part] = {}
26
+ hash.store(part, {})
27
27
  end
28
28
  hash = hash[part]
29
29
  end
@@ -37,9 +37,9 @@ module I18nLine
37
37
  end
38
38
  else
39
39
  @total_size += 1
40
- hash[key] = value
40
+ hash.store(leaf, value)
41
41
  end
42
42
  end
43
43
  end
44
44
  end
45
- end
45
+ end