eex2slime 0.3.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1beaf57748184f55101b8b74c57b4a2543b2ba65
4
+ data.tar.gz: 9debc17d21b9e7c7cb0659d0857a916082cff1e1
5
+ SHA512:
6
+ metadata.gz: e4a18eb89292a887c46d93544bccafe0f1e44396dc3076f964de7fb7f84c4a686158cf8b9078355462eb9535bf362c1bcbccf52e9b98105d940326dbea4313b2
7
+ data.tar.gz: 7a39ad41e70f475b8185f08af92e8cdab386076f80e3aeb1dc4e3ab209028684bc1903c774749cdcc3573704e7f3ce1e86975cf80cd2696bc534f50240df2601
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ ## EEx2Slime
2
+
3
+ Script for converting EEx templates to [Slime](http://slime-lang.com). Slime is a lightweight template language.
4
+
5
+ ## Usage
6
+
7
+ You may convert files using the included executable `eex2slime`.
8
+
9
+ # eex2slime -h
10
+
11
+ Usage: eex2slime INPUT_FILENAME_OR_DIRECTORY [OUTPUT_FILENAME_OR_DIRECTORY] [options]
12
+ --trace Show a full traceback on error
13
+ -d, --delete Delete EEx files
14
+ -h, --help Show this message
15
+ -v, --version Print version
16
+
17
+ Alternatively, to convert files or strings on the fly in your application, you may do so by calling `EEx2Slime.convert!(file)`.
18
+
19
+ ## Installation
20
+
21
+ gem install eex2slime
22
+
23
+ ## Regards
24
+
25
+ Huge thanks to [Maiz Lulkin](https://github.com/joaomilho) and his original [html2slim repo](https://github.com/slim-template/html2slim).
data/bin/eex2slime ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'eex2slime/command'
5
+ cmd = EEx2Slime::Command.new(ARGV)
6
+ cmd.run
@@ -0,0 +1,111 @@
1
+ require 'optparse'
2
+ require 'eex2slime'
3
+
4
+ module EEx2Slime
5
+ class Command
6
+
7
+ def initialize(args)
8
+ @args = args
9
+ @options = {}
10
+ end
11
+
12
+ def run
13
+ @opts = OptionParser.new(&method(:set_opts))
14
+ @opts.parse!(@args)
15
+ process!
16
+ exit 0
17
+ rescue Exception => ex
18
+ raise ex if @options[:trace] || SystemExit === ex
19
+ $stderr.print "#{ex.class}: " if ex.class != RuntimeError
20
+ $stderr.puts ex.message
21
+ $stderr.puts ' Use --trace for backtrace.'
22
+ exit 1
23
+ end
24
+
25
+ protected
26
+
27
+ def format
28
+ :eex
29
+ end
30
+
31
+ def command_name
32
+ :eex2slime
33
+ end
34
+
35
+ def set_opts(opts)
36
+ opts.banner = "Usage: #{command_name} INPUT_FILENAME_OR_DIRECTORY [OUTPUT_FILENAME_OR_DIRECTORY] [options]"
37
+
38
+ opts.on('--trace', :NONE, 'Show a full traceback on error') do
39
+ @options[:trace] = true
40
+ end
41
+
42
+ opts.on_tail('-h', '--help', 'Show this message') do
43
+ puts opts
44
+ exit
45
+ end
46
+
47
+ opts.on_tail('-v', '--version', 'Print version') do
48
+ puts "#{command_name} #{EEx2Slime::VERSION}"
49
+ exit
50
+ end
51
+
52
+ opts.on('-d', '--delete', "Delete #{format.upcase} files") do
53
+ @options[:delete] = true
54
+ end
55
+ end
56
+
57
+ def process!
58
+ args = @args.dup
59
+
60
+ @options[:input] = file = args.shift
61
+ @options[:output] = destination = args.shift
62
+
63
+ @options[:input] = file = "-" unless file
64
+
65
+ if File.directory?(@options[:input])
66
+ Dir["#{@options[:input]}/**/*.#{format}"].each { |file| _process(file, destination) }
67
+ else
68
+ _process(file, destination)
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def input_is_dir?
75
+ File.directory? @options[:input]
76
+ end
77
+
78
+ def _process(file, destination = nil)
79
+ require 'fileutils'
80
+ slime_file = file.sub(/\.#{format}/, '.slime')
81
+
82
+ if input_is_dir? && destination
83
+ FileUtils.mkdir_p(File.dirname(slime_file).sub(@options[:input].chomp('/'), destination))
84
+ slime_file.sub!(@options[:input].chomp('/'), destination)
85
+ else
86
+ slime_file = destination || slime_file
87
+ end
88
+
89
+ if @options[:input] != '-' && file == slime_file
90
+ fail(ArgumentError, "Source and destination files can't be the same.")
91
+ end
92
+
93
+ in_file = if @options[:input] == "-"
94
+ $stdin
95
+ else
96
+ File.open(file, 'r')
97
+ end
98
+
99
+ @options[:output] =
100
+ if slime_file && slime_file != '-'
101
+ File.open(slime_file, 'w')
102
+ else
103
+ $stdout
104
+ end
105
+ @options[:output].puts EEx2Slime.convert!(in_file)
106
+ @options[:output].close
107
+
108
+ File.delete(file) if @options[:delete]
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,99 @@
1
+ require_relative 'hpricot_monkeypatches'
2
+
3
+ module EEx2Slime
4
+ class EExConverter
5
+ def self.from_stream(stream)
6
+ input =
7
+ if stream.is_a?(IO)
8
+ stream.read
9
+ else
10
+ open(stream).read
11
+ end
12
+ new(input)
13
+ end
14
+
15
+ def initialize(input)
16
+ @eex = input
17
+ prepare_control_flow_statements!
18
+ prepare_elixir_anonymous_functions!
19
+ prepare_else_statements!
20
+ prepare_elixir_condition_expressions!
21
+ prepare_end_statements!
22
+ prepare_regular_elixir_code!
23
+ prepare_elixir_inside_attributes!
24
+ @slime = Hpricot(@eex).to_slime
25
+ end
26
+
27
+ def to_s
28
+ @slime
29
+ end
30
+
31
+ private
32
+
33
+ def prepare_curly_blocks!
34
+ @eex.gsub!(/<%(.+?)\s*\{\s*(\|.+?\|)?\s*%>/) {
35
+ %(<%#{$1} do #{$2}%>)
36
+ }
37
+ end
38
+
39
+ def prepare_control_flow_statements!
40
+ @eex.gsub!(/<%(-\s+)?((\s*(case|if|for|unless) .+?)|.+?do\s*(\|.+?\|)?\s*)-?%>/) {
41
+ %(<elixir code="#{$2.gsub(/"/, '&quot;')}">)
42
+ }
43
+ end
44
+
45
+ def prepare_elixir_anonymous_functions!
46
+ @eex.gsub!(/<%(-\s+)?(.+ fn.*->\s*)-?%>/) {
47
+ %(<elixir code="#{$2.gsub(/"/, '&quot;')}">)
48
+ }
49
+ end
50
+
51
+ def prepare_else_statements!
52
+ @eex.gsub!(/<%-?\s*else\s*-?%>/, %(</elixir><elixir code="else">))
53
+ end
54
+
55
+ def prepare_elixir_condition_expressions!
56
+ @eex.gsub!(/<%-?\s*(.* ->)\s*-?%>/) {
57
+ %(<elixir code="#{$1.gsub(/"/, '&quot;')}"></elixir>)
58
+ }
59
+ end
60
+
61
+ def prepare_end_statements!
62
+ @eex.gsub!(/<%=?\s*(end|}|end\s+-)\s*%>/, %(</elixir>))
63
+ end
64
+
65
+ def prepare_regular_elixir_code!
66
+ @eex.gsub!(/<%-?(.+?)\s*-?%>/m) {
67
+ %(<elixir code="#{$1.gsub(/"/, '&quot;')}"></elixir>)
68
+ }
69
+ end
70
+
71
+ # test string
72
+ # <div class="form <%= a() %>" data="<%= b() %>"></div>
73
+ def prepare_elixir_inside_attributes!
74
+ # /
75
+ # =" # ensure we are in attribute value
76
+ # ([^"]*) # match possible other values
77
+ # (
78
+ # <elixir code="= ?
79
+ # ( # match all code inside elixir tag
80
+ # (?:.(?!elixir))+ # but forbid spanning over other attributes
81
+ # )
82
+ # "><\/elixir>
83
+ # )
84
+ # /x
85
+ #
86
+ # Example match data:
87
+ # Match 1
88
+ # 1. form
89
+ # 2. <elixir code="= a()"></elixir>
90
+ # 3. a()
91
+ # Match 2
92
+ # 1.
93
+ # 2. <elixir code="= b()"></elixir>
94
+ # 3. b()
95
+ regex = /="([^"]*)(<elixir code="= ?((?:.(?!elixir))+)"><\/elixir>)/
96
+ @eex.gsub!(regex, '="\\1#{\\3}')
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,183 @@
1
+ require 'hpricot'
2
+
3
+ Hpricot::XHTMLTransitional.tagset[:elixir] = [:code]
4
+
5
+ module SlimText
6
+ def to_slime(lvl=0)
7
+ return nil if textify.strip.empty?
8
+ (' ' * lvl) + %(| #{textify.gsub(/\s+/, ' ').strip})
9
+ end
10
+
11
+ # default implementation
12
+ def textify
13
+ to_s
14
+ end
15
+ end
16
+
17
+ module BlankSlim
18
+ def to_slime(_lvl=0)
19
+ nil
20
+ end
21
+ end
22
+
23
+ class Hpricot::CData
24
+ include SlimText
25
+ end
26
+ class Hpricot::XMLDecl
27
+ include SlimText
28
+ end
29
+ class Hpricot::Attributes
30
+ include SlimText
31
+ end
32
+
33
+ class Hpricot::Text
34
+ include SlimText
35
+
36
+ def textify
37
+ content.to_s
38
+ end
39
+ end
40
+
41
+ class Hpricot::BogusETag
42
+ include BlankSlim
43
+ end
44
+
45
+ class Hpricot::Comment
46
+ include BlankSlim
47
+ end
48
+
49
+ class Hpricot::DocType
50
+ def to_slime(lvl=0)
51
+ if to_s.include? "xml"
52
+ to_s.include?("iso-8859-1") ? "doctype xml ISO-88591" : "doctype xml"
53
+ elsif to_s.include? "XHTML" or self.to_s.include? "HTML 4.01"
54
+ available_versions = Regexp.union ["Basic", "1.1", "strict", "Frameset", "Mobile", "Transitional"]
55
+ version = to_s.match(available_versions).to_s.downcase
56
+ "doctype #{version}"
57
+ else
58
+ "doctype html"
59
+ end
60
+ end
61
+ end
62
+
63
+ class Hpricot::Elem
64
+ BLANK_RE = /\A[[:space:]]*\z/
65
+
66
+ def slime(lvl=0)
67
+ r = ' ' * lvl
68
+
69
+ return r + slime_elixir_code(r) if elixir?
70
+
71
+ r += name unless skip_tag_name?
72
+ r += slime_id
73
+ r += slime_class
74
+ r += slime_attributes
75
+ r
76
+ end
77
+
78
+ def to_slime(lvl=0)
79
+ if respond_to?(:children) and children
80
+ [slime(lvl), children_slime(lvl)].join("\n")
81
+ else
82
+ slime(lvl)
83
+ end
84
+ end
85
+
86
+ def elixir?
87
+ name == "elixir"
88
+ end
89
+
90
+ private
91
+
92
+ def children_slime(lvl)
93
+ children
94
+ .map { |c| c.to_slime(lvl+1) }
95
+ .select { |e| !e.nil? }
96
+ .join("\n")
97
+ end
98
+
99
+ def slime_elixir_code(r)
100
+ lines = code.lines.drop_while { |line| line.strip.empty? }
101
+ indent_level = lines.first.match(/^ */)[0].length
102
+ prettified_lines = lines.map do |line|
103
+ line.slice(indent_level .. -1).rstrip
104
+ end
105
+ prettified = prettified_lines.join(" \\\n#{r} ")
106
+
107
+ first_symbol = code.strip[0] == "=" ? "" : "- "
108
+ first_symbol + prettified
109
+ end
110
+
111
+ def code
112
+ attributes["code"]
113
+ end
114
+
115
+ def skip_tag_name?
116
+ div? && (has_id? || non_cryptic_classes.any?)
117
+ end
118
+
119
+ def slime_id
120
+ return "" unless has_id?
121
+ "#" << self['id']
122
+ end
123
+
124
+ def slime_class
125
+ return "" unless has_class?
126
+ return "" if non_cryptic_classes.empty?
127
+ ".#{non_cryptic_classes.join('.')}"
128
+ end
129
+
130
+ def slime_attributes
131
+ remove_css_class
132
+ remove_attribute('id')
133
+ has_attributes? ? "[#{attributes_as_html.to_s.strip}]" : ""
134
+ end
135
+
136
+ def has_attributes?
137
+ attributes.to_hash.any?
138
+ end
139
+
140
+ def has_id?
141
+ has_attribute?('id') && !(BLANK_RE === self['id'])
142
+ end
143
+
144
+ def has_class?
145
+ has_attribute?('class') && !(BLANK_RE === self['class'])
146
+ end
147
+
148
+ def div?
149
+ name == "div"
150
+ end
151
+
152
+ def remove_css_class
153
+ if has_class? && cryptic_classes.any?
154
+ self["class"] = cryptic_classes.join(" ")
155
+ else
156
+ remove_attribute('class')
157
+ end
158
+ end
159
+
160
+ def non_cryptic_classes
161
+ return [] unless has_attribute?('class')
162
+ classes = self['class'].strip.split(/\s+/)
163
+ classes.reject { |s| s.match(/[=#]/) }
164
+ end
165
+
166
+ # We can have interpolation inside attributes.
167
+ # Such classes should always be in attributes section (not shortened)
168
+ def cryptic_classes
169
+ return [] unless has_attribute?('class')
170
+ classes = self['class'].strip.split(/\s+/)
171
+ classes.select { |s| s.match(/[=#]/) }
172
+ end
173
+ end
174
+
175
+ class Hpricot::Doc
176
+ def to_slime
177
+ if respond_to?(:children) && children
178
+ children.map(&:to_slime).reject(&:nil?).join("\n")
179
+ else
180
+ ''
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,3 @@
1
+ module EEx2Slime
2
+ VERSION = '0.3.0'
3
+ end
data/lib/eex2slime.rb ADDED
@@ -0,0 +1,8 @@
1
+ require_relative 'eex2slime/version'
2
+ require_relative 'eex2slime/converter'
3
+
4
+ module EEx2Slime
5
+ def self.convert!(input)
6
+ EExConverter.from_stream(input).to_s
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eex2slime
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Anton Chuchkalov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hpricot
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12'
55
+ description: Make your templates lightweight by converting them from EEx to Slime.
56
+ email:
57
+ - hedgesky@gmail.com
58
+ executables:
59
+ - eex2slime
60
+ extensions: []
61
+ extra_rdoc_files:
62
+ - README.md
63
+ files:
64
+ - README.md
65
+ - bin/eex2slime
66
+ - lib/eex2slime.rb
67
+ - lib/eex2slime/command.rb
68
+ - lib/eex2slime/converter.rb
69
+ - lib/eex2slime/hpricot_monkeypatches.rb
70
+ - lib/eex2slime/version.rb
71
+ homepage: https://github.com/hedgesky/eex2slime
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options:
77
+ - "--charset=UTF-8"
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.6.10
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: EEx to Slime converter.
96
+ test_files: []