herbgobbler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/bin/gobble +43 -0
  2. data/grammer/erb_grammer.treetop +256 -0
  3. data/lib/core/base_node.rb +29 -0
  4. data/lib/core/base_text_extractor.rb +19 -0
  5. data/lib/core/base_translation_store.rb +49 -0
  6. data/lib/core/core.rb +17 -0
  7. data/lib/core/double_quoted_text_node.rb +89 -0
  8. data/lib/core/erb_file.rb +159 -0
  9. data/lib/core/erb_string_non_text_content.rb +8 -0
  10. data/lib/core/i18n_key.rb +74 -0
  11. data/lib/core/ignorable_tag_node.rb +10 -0
  12. data/lib/core/method_call_node.rb +44 -0
  13. data/lib/core/node_processing.rb +26 -0
  14. data/lib/core/non_extracting_non_text_node.rb +8 -0
  15. data/lib/core/non_text_node.rb +32 -0
  16. data/lib/core/rails_text_extractor.rb +88 -0
  17. data/lib/core/rails_translation_store.rb +42 -0
  18. data/lib/core/text_extractor.rb +31 -0
  19. data/lib/core/text_node.rb +73 -0
  20. data/lib/herbgobbler.rb +7 -0
  21. data/lib/nodes/combindable_herb_non_text_node.rb +5 -0
  22. data/lib/nodes/herb_combined_node.rb +47 -0
  23. data/lib/nodes/herb_erb_text_call_node.rb +52 -0
  24. data/lib/nodes/herb_node_retaining_node.rb +37 -0
  25. data/lib/nodes/herb_node_retaining_non_text_node.rb +30 -0
  26. data/lib/nodes/herb_node_retaining_text_node.rb +107 -0
  27. data/lib/nodes/herb_non_text_node.rb +21 -0
  28. data/lib/nodes/herb_string_variable.rb +9 -0
  29. data/lib/nodes/herb_text_node.rb +23 -0
  30. data/lib/nodes/herb_white_space_text_node.rb +14 -0
  31. data/lib/nodes/nodes.rb +13 -0
  32. data/lib/nodes/rails_text_variable_node.rb +19 -0
  33. data/scripts/display_syntax_tree.rb +18 -0
  34. data/scripts/extract_text_from_erb.rb +12 -0
  35. data/scripts/extract_yml_from_erb.rb +12 -0
  36. data/scripts/rails_gobble_file.rb +37 -0
  37. data/scripts/save_result.rb +11 -0
  38. metadata +154 -0
@@ -0,0 +1,74 @@
1
+ class I18nKey
2
+
3
+ DEFAULT_KEY_NAME = "key"
4
+
5
+ def initialize( text, key_store = [] )
6
+ @text = text
7
+ @key_store = key_store
8
+ @key_value = nil
9
+ end
10
+
11
+ def key_value
12
+ if( @key_value.nil? )
13
+ to_return = remove_html_tags( @text )
14
+ to_return = to_return.gsub( /[%{].*?[}]/, '' )
15
+ to_return = to_return.gsub( /[^a-zA-Z0-9]/, ' ' )
16
+ to_return = to_return.gsub( /[ ]+/, ' ' )
17
+ to_return = to_return.strip.downcase.chomp
18
+ to_return = to_return.gsub( / /, '_' )
19
+ to_return = cut_down_to_size( to_return )
20
+ to_return = ensure_no_duplicates( to_return )
21
+ self.key_value = to_return
22
+ end
23
+ @key_value
24
+ end
25
+
26
+ def key_value=(value)
27
+ @key_value = value
28
+ @key_store << value
29
+ end
30
+
31
+ def to_s
32
+ key_value
33
+ end
34
+
35
+ private
36
+
37
+ def cut_down_to_size( string_to_remove )
38
+ if( string_to_remove.length < 15 )
39
+ string_to_remove
40
+ else
41
+ if( string_to_remove.rindex( '_' ) )
42
+ cut_down_to_size( string_to_remove[0..(string_to_remove.rindex('_')-1) ] )
43
+ else
44
+ string_to_remove
45
+ end
46
+ end
47
+ end
48
+
49
+ def exists_in_keystore?( value )
50
+ !(@key_store.index( value ).nil?)
51
+ end
52
+
53
+ def ensure_no_duplicates( key_value, incrementor = 1 )
54
+ if( key_value.strip.empty? )
55
+ ensure_no_duplicates( DEFAULT_KEY_NAME )
56
+ elsif( exists_in_keystore?( key_value ) )
57
+ incremented_value = "#{key_value}_#{incrementor}"
58
+ if( exists_in_keystore?( incremented_value ) )
59
+ ensure_no_duplicates( key_value, incrementor + 1 )
60
+ else
61
+ incremented_value
62
+ end
63
+ else
64
+ key_value
65
+ end
66
+ end
67
+
68
+ def remove_html_tags( string_to_remove )
69
+ string_to_remove.gsub( /<[\/]?[a-zA-Z]+[^<]*>/, '_')
70
+ end
71
+
72
+
73
+
74
+ end
@@ -0,0 +1,10 @@
1
+ module IgnorableTagNode
2
+ def text?
3
+ false
4
+ end
5
+
6
+ def top_level?
7
+ true
8
+ end
9
+
10
+ end
@@ -0,0 +1,44 @@
1
+ module MethodCallNode
2
+ include TextNode
3
+
4
+ def extract_text( text_extractor, node_tree, surrounding_nodes )
5
+
6
+ text_string = ''
7
+ self.elements.each do |node|
8
+ if( node.is_a?( TextNode ) )
9
+ translated_node = text_extractor.translate_text( node.text_value )
10
+ text_string << "(#{translated_node.text_value})"
11
+ else
12
+ text_string << node.text_value
13
+ end
14
+ end
15
+
16
+ if( surrounded_by_text? (surrounding_nodes) )
17
+ text_extractor.add_variable( generate_i18n_key( text_extractor, node_tree ).to_s, text_string.strip )
18
+ else
19
+ node_tree << HerbNonTextNode.new( "<%= #{text_string.strip} %>" )
20
+ end
21
+
22
+ end
23
+
24
+ private
25
+
26
+ def surrounded_by_text?( nodes )
27
+ # if the surrounding nodes only contain myself and <%= and %> then this
28
+ # is not surrounded by text
29
+ count_non_whitespace_nodes( nodes ) > 3
30
+ end
31
+
32
+ def count_non_whitespace_nodes( nodes )
33
+ node_count = 0
34
+
35
+ nodes.each do |node|
36
+ node_count += 1 unless node.text_value.strip.empty?
37
+ end
38
+
39
+ node_count
40
+ end
41
+
42
+
43
+
44
+ end
@@ -0,0 +1,26 @@
1
+ module NodeProcessing
2
+
3
+
4
+ def flatten(node, leaves = [])
5
+ # This finds all of the leaves, where a leaf is defined as an
6
+ # element with no sub elements. This also treats combindable nodes
7
+ # and text nodes as leaves because we want to keep them intact for
8
+ # extraction and combination
9
+ if( !node.respond_to?( :elements) ||
10
+ node.elements.nil? ||
11
+ node.elements.empty? ||
12
+ (node.is_a?( TextNode ) && !node.has_variables?)||
13
+ node.is_a?( HerbStringVariable ) ||
14
+ ( node.is_a?(NonTextNode) && node.can_be_combined? ) )
15
+ leaves << node if( !node.text_value.empty? )
16
+ leaves
17
+ else
18
+ node.elements.each do |sub_node|
19
+ flatten( sub_node, leaves )
20
+ end
21
+ leaves
22
+ end
23
+ end
24
+
25
+
26
+ end
@@ -0,0 +1,8 @@
1
+ module NonExtractingNonTextNode
2
+ include NonTextNode
3
+
4
+ def extract_text( text_extractor, node_tree )
5
+ # I do nothing!
6
+ end
7
+
8
+ end
@@ -0,0 +1,32 @@
1
+ module NonTextNode
2
+ include BaseNode
3
+ def can_be_combined?
4
+ false
5
+ end
6
+
7
+ def extract_text( text_extractor, node_tree )
8
+ if( self.white_space? )
9
+ text_extractor.white_space( self )
10
+ else
11
+ text_extractor.add_non_text( self ) unless self.contains_only_whitespace?
12
+ end
13
+
14
+ end
15
+
16
+ def contains_only_whitespace?
17
+ self.text_value.strip.length == 0
18
+ end
19
+
20
+ def text?
21
+ false
22
+ end
23
+
24
+ def top_level?
25
+ true
26
+ end
27
+
28
+ def white_space?
29
+ false
30
+ end
31
+
32
+ end
@@ -0,0 +1,88 @@
1
+ class RailsTextExtractor < BaseTextExtractor
2
+ attr_reader :translation_store
3
+
4
+ def initialize( translation_store = RailsTranslationStore.new )
5
+ @key_store = []
6
+ @translation_store = translation_store
7
+ @current_text = []
8
+ @current_nodes = []
9
+ @current_variables = []
10
+ end
11
+
12
+ # This is called when text extraction has begun
13
+ def starting_text_extraction
14
+ end
15
+
16
+ def start_html_text
17
+ @current_text = []
18
+ @current_nodes = []
19
+ @current_variables = []
20
+ end
21
+
22
+ def end_html_text
23
+ whitespace, @current_text = strip_ending_whitespace_nodes( @current_text )
24
+
25
+ unless( @current_text.empty? && @current_variables.empty? )
26
+ total_text = @current_text.inject("") { |all_text, node| all_text + node.text_value }
27
+ @current_nodes << HerbErbTextCallNode.new( [total_text], @key_store, "t '.", "'", @current_variables )
28
+ @translation_store.add_translation( @current_nodes.last.key_value, @current_nodes.last.original_text )
29
+ end
30
+
31
+ # Just reset everything here to be cautious
32
+ to_return = @current_nodes
33
+ @current_nodes = []
34
+ @current_text = []
35
+
36
+ return to_return + whitespace
37
+ end
38
+
39
+ def white_space( node )
40
+ if( @current_text.empty? )
41
+ @current_nodes << node
42
+ else
43
+ @current_text << node
44
+ end
45
+ end
46
+
47
+ # This is used to add a new variable into the current html element
48
+ def add_variable( variable_name, variable_value)
49
+ variable_node = RailsTextVariableNode.new( variable_name, variable_value )
50
+ @current_text << variable_node
51
+ @current_variables << variable_node
52
+ end
53
+
54
+ # This is called to just produce a translated text node without erb
55
+ # braces around it.
56
+ def translate_text( text_to_translate )
57
+ call_node = HerbErbTextCallNode.new( [text_to_translate], @key_store, "t '.", "'", [], false )
58
+ @translation_store.add_translation( call_node.key_value, call_node.original_text )
59
+ call_node
60
+ end
61
+
62
+ # This takes in a text node and returns one or more nodes that will
63
+ # then be output. The nodes that are output should implement
64
+ # node_name and text_value
65
+ def add_html_text( text_node )
66
+ @current_text << text_node
67
+ end
68
+
69
+
70
+ def add_non_text( non_text_node )
71
+ @current_text << non_text_node
72
+ end
73
+
74
+ # This is called when text extraction has finished
75
+ def completed_text_extraction
76
+ end
77
+
78
+ protected
79
+
80
+ def strip_ending_whitespace_nodes( node_list )
81
+ whitespace = []
82
+ while( !node_list.empty? && node_list.last.white_space? )
83
+ whitespace << node_list.pop
84
+ end
85
+ return whitespace, node_list
86
+ end
87
+
88
+ end
@@ -0,0 +1,42 @@
1
+ class RailsTranslationStore < BaseTranslationStore
2
+
3
+ def initialize( language = "en" )
4
+ super()
5
+ @language = language
6
+ end
7
+
8
+ # This goes out of it's way to keep things in exactly the correct
9
+ # order. This makes it easier for translators when dealing with
10
+ # files. This is the reason to not just throw things into a hash
11
+ # and serialize the hash
12
+ def serialize
13
+ to_return = "#{@language}:\n"
14
+ last_context = nil
15
+ self.each do |context, key, value|
16
+ whitespace_depth = 2
17
+ context_array = context.split('/')
18
+ context_array.each do |split_context|
19
+ to_return << "#{build_whitespace( whitespace_depth )}#{split_context}:\n"
20
+ whitespace_depth += 2
21
+ end unless context == last_context
22
+ to_return << "#{build_whitespace( 2 + 2 * context_array.length )}#{key}: \"#{escape(value)}\"\n"
23
+ last_context = context
24
+ end
25
+
26
+ to_return.chomp
27
+ end
28
+
29
+ private
30
+
31
+ def build_whitespace( amount )
32
+ " " * amount
33
+ end
34
+
35
+ private
36
+
37
+
38
+ def escape( key )
39
+ key.gsub( /"/, '\"' ).gsub( "\n", "\\n" )
40
+ end
41
+
42
+ end
@@ -0,0 +1,31 @@
1
+ # This is only meant to be a base class, please extend and implement
2
+ # the base methods if you want to do anything interesting
3
+ class TextExtractor
4
+
5
+ # This is called when text extraction has begun
6
+ def starting_text_extraction
7
+ raise "Please implement me (start_text_extraction)"
8
+ end
9
+
10
+ def add_html_text( text_node )
11
+ raise "Please implement me (add_html_text)"
12
+ end
13
+
14
+ def add_variable( variable_name, variable_value )
15
+ raise "Please implement me (add_variable)"
16
+ end
17
+
18
+ def translate_text( text_node_to_translate )
19
+ raise "Please impelement me (translate_text)"
20
+ end
21
+
22
+ def end_html_text
23
+ raise "Please implement me (end_html_text)"
24
+ end
25
+
26
+ def start_html_text
27
+ raise "Please implement me (start_html_text)"
28
+ end
29
+
30
+
31
+ end
@@ -0,0 +1,73 @@
1
+ module TextNode
2
+ include BaseNode
3
+ include NodeProcessing
4
+
5
+ def extract_text( text_extractor, node_tree )
6
+ if( self.strip_whitespace? && self.has_leading_or_trailing_whitespace? )
7
+ self.remove_leading_and_trailing_whitespace.each do |node|
8
+ node.extract_text( text_extractor, node_tree )
9
+ end
10
+ else
11
+ text_extractor.add_html_text( self )
12
+ end
13
+ end
14
+
15
+ def has_variables?
16
+ false
17
+ end
18
+
19
+ def html?
20
+ true
21
+ end
22
+
23
+ def strip_whitespace?
24
+ true
25
+ end
26
+
27
+ def text?
28
+ true
29
+ end
30
+
31
+ def top_level?
32
+ true
33
+ end
34
+
35
+ def white_space?
36
+ false
37
+ end
38
+
39
+
40
+ def amount_of_ending_whitespace
41
+ self.text_value.length - self.text_value.rstrip.length
42
+ end
43
+
44
+ def amount_of_starting_whitespace
45
+ self.text_value.length - self.text_value.lstrip.length
46
+ end
47
+
48
+ def has_leading_or_trailing_whitespace?
49
+ amount_of_ending_whitespace > 0 || amount_of_starting_whitespace > 0
50
+ end
51
+
52
+ # This probably won't work, because I want it to retain all of the
53
+ # nodes. Maybe we should run this after the text extraction
54
+ # happens? Nope, that won't work....
55
+ def remove_leading_and_trailing_whitespace
56
+ to_return = []
57
+ if( self.text_value.strip.empty? ) # is this only whitespace
58
+ to_return << HerbWhiteSpaceTextNode.new( self.text_value )
59
+ else # otherwise it has some text
60
+ if( amount_of_starting_whitespace > 0 )
61
+ to_return << HerbWhiteSpaceTextNode.new( self.text_value[0..(amount_of_starting_whitespace - 1 )] )
62
+ end
63
+ to_return << HerbTextNode.new( self.text_value.strip )
64
+ if( amount_of_ending_whitespace > 0 )
65
+ to_return << HerbWhiteSpaceTextNode.new( self.text_value[(self.text_value.length - amount_of_ending_whitespace)..self.text_value.length ])
66
+ end
67
+ end
68
+
69
+ to_return
70
+ end
71
+
72
+
73
+ end
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
2
+ $ERB_GRAMMER_FILE = File.expand_path( "#{File.expand_path( File.dirname( __FILE__) )}/../grammer/erb_grammer.treetop" )
3
+
4
+ require 'rubygems'
5
+ require 'treetop'
6
+ require 'core/core'
7
+ require 'nodes/nodes'
@@ -0,0 +1,5 @@
1
+ class CombindableHerbNonTextNode < HerbNonTextNode
2
+ def can_be_combined?
3
+ true
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ class HerbCombinedNode
2
+
3
+ def initialize( node_a, node_b )
4
+ @combined_nodes = [node_a, node_b]
5
+ end
6
+
7
+ def node_name
8
+ "herb_combined_nodes"
9
+ end
10
+
11
+ def can_be_combined?
12
+ true
13
+ end
14
+
15
+ def should_be_unrolled?
16
+ @combined_nodes.last.white_space?
17
+ end
18
+
19
+ def html?
20
+ true
21
+ end
22
+
23
+ def text?
24
+ @combined_nodes.first.text? || @combined_nodes.last.text?
25
+ end
26
+
27
+ def text_value
28
+ to_return = "";
29
+ @combined_nodes.each do |node|
30
+ to_return << node.text_value
31
+ end
32
+ to_return
33
+ end
34
+
35
+ def to_s
36
+ self.text_value
37
+ end
38
+
39
+ def whitespace?
40
+ @combined_nodes.first.whitespace? || @combined_nodes.last.whitespace?
41
+ end
42
+
43
+ def unroll
44
+ @combined_nodes
45
+ end
46
+
47
+ end
@@ -0,0 +1,52 @@
1
+ class HerbErbTextCallNode
2
+
3
+ def initialize( text_values = [], key_store = [], prepend = nil, postpend = nil, variables = [], include_markup = true )
4
+ @text_values = text_values
5
+ @prepend = prepend
6
+ @postpend = postpend
7
+ @key_store = key_store
8
+ @key_value = nil
9
+ @include_markup = include_markup
10
+ @variables = variables
11
+ end
12
+
13
+ def add_text( text )
14
+ @text_values << text
15
+ end
16
+
17
+ def can_be_combined?
18
+ true
19
+ end
20
+
21
+ def key_value
22
+ if( @key_value.nil? )
23
+ @key_value = I18nKey.new( original_text, @key_store ).key_value
24
+ end
25
+
26
+ @key_value
27
+ end
28
+
29
+ def node_name
30
+ "herb_erb_text_call_node"
31
+ end
32
+
33
+ def original_text
34
+ @text_values.join( '' )
35
+ end
36
+
37
+ def text_value
38
+ string_to_return = "#{@prepend}#{key_value}#{@postpend}"
39
+ string_to_return += ", #{@variables.join( ', ' )}" unless @variables.empty?
40
+ if( @include_markup )
41
+ "<%= #{string_to_return} %>"
42
+ else
43
+ string_to_return
44
+ end
45
+
46
+ end
47
+
48
+ def to_s
49
+ text_value
50
+ end
51
+
52
+ end
@@ -0,0 +1,37 @@
1
+ class HerbNodeRetainingNode
2
+
3
+ def initialize( first_node = nil )
4
+ @sub_nodes = []
5
+ @sub_nodes << first_node unless first_node.nil?
6
+ end
7
+
8
+ def nodes
9
+ to_return = []
10
+ @sub_nodes.each do|node|
11
+ if( node.is_a?( HerbNodeRetainingNode ) )
12
+ to_return += node.nodes
13
+ else
14
+ to_return << node
15
+ end
16
+ end
17
+ to_return
18
+ end
19
+
20
+ def <<(node)
21
+ if( node.is_a?(Array) )
22
+ @sub_nodes+= node
23
+ else
24
+ @sub_nodes << node
25
+ end
26
+ self
27
+ end
28
+
29
+ def text_value
30
+ to_return = ""
31
+ @sub_nodes.each do |node|
32
+ to_return += node.text_value
33
+ end
34
+ to_return
35
+ end
36
+
37
+ end
@@ -0,0 +1,30 @@
1
+ class HerbNodeRetainingNonTextNode < HerbNodeRetainingNode
2
+ include NonTextNode
3
+
4
+ def initialize( first_node = nil )
5
+ super( first_node )
6
+ @can_be_combined = false
7
+ end
8
+
9
+ def <<(node)
10
+ super(node)
11
+ @can_be_combined ||= ( node.respond_to?( :can_be_combined? ) && node.can_be_combined? )
12
+ end
13
+
14
+ def can_be_combined?
15
+ @can_be_combined
16
+ end
17
+
18
+ def self.create_from_nodes( nodes )
19
+ to_return = HerbNodeRetainingNonTextNode.new
20
+ nodes.each do |node|
21
+ to_return << node
22
+ end
23
+ to_return
24
+ end
25
+
26
+
27
+
28
+
29
+
30
+ end