herbgobbler 0.1.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.
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