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 +7 -0
- data/README.md +25 -0
- data/bin/eex2slime +6 -0
- data/lib/eex2slime/command.rb +111 -0
- data/lib/eex2slime/converter.rb +99 -0
- data/lib/eex2slime/hpricot_monkeypatches.rb +183 -0
- data/lib/eex2slime/version.rb +3 -0
- data/lib/eex2slime.rb +8 -0
- metadata +96 -0
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,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(/"/, '"')}">)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def prepare_elixir_anonymous_functions!
|
46
|
+
@eex.gsub!(/<%(-\s+)?(.+ fn.*->\s*)-?%>/) {
|
47
|
+
%(<elixir code="#{$2.gsub(/"/, '"')}">)
|
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(/"/, '"')}"></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(/"/, '"')}"></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
|
data/lib/eex2slime.rb
ADDED
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: []
|