shift 0.1.0 → 0.4.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.
- data/CHANGELOG +20 -0
- data/README.md +105 -37
- data/Rakefile +1 -1
- data/TODO +3 -0
- data/bin/shifter +5 -0
- data/lib/shift.rb +42 -36
- data/lib/shift/action_map.rb +106 -0
- data/lib/shift/cli.rb +43 -0
- data/lib/shift/errors.rb +7 -3
- data/lib/shift/i/closure_compiler.rb +23 -0
- data/lib/shift/{c → i}/coffee_script.rb +6 -3
- data/lib/shift/i/echo.rb +9 -0
- data/lib/shift/{c → i}/rdiscount.rb +6 -2
- data/lib/shift/{c → i}/redcarpet.rb +6 -2
- data/lib/shift/{c → i}/sass.rb +6 -2
- data/lib/shift/{c → i}/uglify_js.rb +7 -3
- data/lib/shift/{c → i}/yui_compressor.rb +7 -3
- data/lib/shift/i/zlib_reader.rb +23 -0
- data/lib/shift/i/zlib_writer.rb +27 -0
- data/lib/shift/{c/identity.rb → interface.rb} +39 -42
- data/lib/shift/interface_list.rb +67 -0
- data/lib/shift/interfaces.rb +20 -0
- data/lib/shift/mapper.rb +95 -0
- data/lib/shift/mappings.rb +51 -28
- data/lib/shift/mappings.yml +6 -0
- data/lib/shift/string.rb +125 -0
- data/shift.gemspec +3 -2
- data/test/data/letter.echo +0 -5
- data/test/helper.rb +2 -12
- data/test/{c → i}/closure_compiler_test.rb +6 -2
- data/test/{c → i}/coffee_script_test.rb +6 -2
- data/test/i/rdiscount_test.rb +22 -0
- data/test/i/redcarpet_test.rb +22 -0
- data/test/i/sass_test.rb +19 -0
- data/test/{c → i}/uglify_js_test.rb +6 -2
- data/test/{c → i}/yui_compressor_test.rb +6 -2
- data/test/i/zlib_reader.rb +24 -0
- data/test/interface_test.rb +60 -0
- data/test/mapper_test.rb +144 -0
- data/test/shift_test.rb +12 -43
- data/test/string_test.rb +39 -0
- metadata +39 -24
- data/lib/shift/c/closure_compiler.rb +0 -19
- data/test/c/identity_test.rb +0 -79
- data/test/c/rdiscount_test.rb +0 -18
- data/test/c/redcarpet_test.rb +0 -18
- data/test/c/sass_test.rb +0 -14
- data/test/support.rb +0 -37
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Shift
|
4
|
+
|
5
|
+
autoload :Interface, 'shift/interface'
|
6
|
+
|
7
|
+
autoload :Echo, 'shift/i/echo'
|
8
|
+
autoload :UglifyJS, 'shift/i/uglify_js'
|
9
|
+
autoload :ClosureCompiler, 'shift/i/closure_compiler'
|
10
|
+
autoload :YUICompressor, 'shift/i/yui_compressor'
|
11
|
+
autoload :CoffeeScript, 'shift/i/coffee_script'
|
12
|
+
autoload :Sass, 'shift/i/sass'
|
13
|
+
autoload :RDiscount, 'shift/i/rdiscount'
|
14
|
+
autoload :Redcarpet, 'shift/i/redcarpet'
|
15
|
+
autoload :RedCarpet, 'shift/i/redcarpet'
|
16
|
+
|
17
|
+
autoload :ZlibReader, 'shift/i/zlib_reader'
|
18
|
+
autoload :ZlibWriter, 'shift/i/zlib_writer'
|
19
|
+
|
20
|
+
end
|
data/lib/shift/mapper.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
module Shift
|
3
|
+
|
4
|
+
require 'shift/interface_list'
|
5
|
+
require 'shift/action_map'
|
6
|
+
|
7
|
+
MAP = {}
|
8
|
+
|
9
|
+
# Herein lies the logic concerned with mapping (format, action)
|
10
|
+
# to compiler interfaces.
|
11
|
+
#
|
12
|
+
class << self
|
13
|
+
|
14
|
+
# Global actions that apply to all types
|
15
|
+
#
|
16
|
+
def global
|
17
|
+
@global ||= ActionMap.new(nil)
|
18
|
+
end
|
19
|
+
attr_writer :global
|
20
|
+
|
21
|
+
# Register mappings for file types.
|
22
|
+
#
|
23
|
+
def map(*synonyms)
|
24
|
+
actions = synonyms.pop
|
25
|
+
|
26
|
+
synonyms.each do |syn|
|
27
|
+
am = MAP[syn.to_s] ||= ActionMap.new(global, actions)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the preferred available class mapped to the given format
|
32
|
+
# or path and action.
|
33
|
+
#
|
34
|
+
def [](name, action=:default)
|
35
|
+
try_to_match(name) do |fmt|
|
36
|
+
|
37
|
+
if action_map = MAP[fmt]
|
38
|
+
if iface_list = action_map[action]
|
39
|
+
return iface_list.pick
|
40
|
+
else
|
41
|
+
raise UnknownActionError,
|
42
|
+
"#{action.inspect} with format #{name.inspect}."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return global[action].pick if global[action]
|
46
|
+
end
|
47
|
+
raise UnknownFormatError, "no mappings for #{name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def synonyms
|
52
|
+
MAP.group_by do |name, actions|
|
53
|
+
actions
|
54
|
+
end.map do |action_map, hsh_ary|
|
55
|
+
hsh_ary.map {|k,v| k }.flatten.uniq
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def action_overview(globals=false)
|
60
|
+
result = {}
|
61
|
+
synonyms.each do |group|
|
62
|
+
result[group] = group.map do |fmt|
|
63
|
+
MAP[fmt].atoms(globals).keys
|
64
|
+
end.uniq
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect_actions
|
70
|
+
buf = []
|
71
|
+
buf << "GLOBAL: #{global.atoms(true).keys.join(', ')}"
|
72
|
+
|
73
|
+
action_overview.each do |types, actions|
|
74
|
+
buf << "#{types.join(', ')}: #{actions.join(', ')}"
|
75
|
+
end
|
76
|
+
|
77
|
+
buf.join("\n")
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Given a file name, yield all the formats it could match.
|
83
|
+
#
|
84
|
+
def try_to_match(name)
|
85
|
+
return yield name if name.nil?
|
86
|
+
|
87
|
+
pattern = File.basename(name.to_s.downcase)
|
88
|
+
until pattern.empty?
|
89
|
+
yield pattern
|
90
|
+
pattern.sub!(/^[^.]*\.?/, '')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
data/lib/shift/mappings.rb
CHANGED
@@ -1,32 +1,55 @@
|
|
1
1
|
|
2
2
|
module Shift
|
3
|
-
|
4
|
-
# Mappings from file names to implementation classes. The
|
5
|
-
# classes are listed in order of preference per type.
|
6
|
-
#
|
7
|
-
MAPPINGS = {
|
8
|
-
'echo' => %w{ Identity },
|
9
|
-
'js' => %w{ UglifyJS ClosureCompiler YUICompressor },
|
10
|
-
'coffee' => %w{ CoffeeScript },
|
11
|
-
'sass' => %w{ Sass },
|
12
|
-
'md' => %w{ RDiscount Redcarpet }
|
13
|
-
}
|
14
|
-
|
15
|
-
# @raise [DependencyError] when none of the mapped
|
16
|
-
# implementations are available.
|
17
|
-
#
|
18
|
-
# @return [Class] The preferred available class associated
|
19
|
-
# with the file or extension.
|
20
|
-
#
|
21
|
-
def self.best_available_mapping_for(key)
|
22
|
-
MAPPINGS[key].each do |kls_name|
|
23
|
-
kls = const_get(kls_name)
|
24
|
-
return kls if kls.available?
|
25
|
-
end
|
26
|
-
help = const_get(MAPPINGS[key].first)::INSTRUCTIONS
|
27
|
-
raise DependencyError, "no implementation available for " +
|
28
|
-
"#{key.inspect}. Possible solution: #{help}"
|
29
|
-
end
|
30
|
-
end
|
31
3
|
|
4
|
+
global.map(
|
5
|
+
:gzip => 'ZlibWriter'
|
6
|
+
)
|
7
|
+
|
8
|
+
map(:echo, 'Echo')
|
9
|
+
|
10
|
+
map(:coffee,
|
11
|
+
:default => :compile,
|
12
|
+
:compile => 'CoffeeScript'
|
13
|
+
)
|
14
|
+
|
15
|
+
#map(:haml,
|
16
|
+
# :default => :compile,
|
17
|
+
# :compile => 'Haml'
|
18
|
+
# )
|
19
|
+
|
20
|
+
#map('haml.compiled',
|
21
|
+
# :default => :render,
|
22
|
+
# :render => 'HamlTemplate'
|
23
|
+
# )
|
24
|
+
|
25
|
+
#map(:rbt,
|
26
|
+
# :default => :render,
|
27
|
+
# :render => 'RubyTemplate'
|
28
|
+
# )
|
29
|
+
|
30
|
+
map(:gz,
|
31
|
+
:default => :inflate,
|
32
|
+
:decompress => :inflate,
|
33
|
+
:unzip => :inflate,
|
34
|
+
:inflate => 'ZlibReader'
|
35
|
+
)
|
36
|
+
|
37
|
+
map(:js,
|
38
|
+
:default => :compress,
|
39
|
+
:minify => :compress,
|
40
|
+
:compress => %w{UglifyJS YUICompressor ClosureCompiler}
|
41
|
+
)
|
42
|
+
|
43
|
+
map(:md, :markdown,
|
44
|
+
:default => :render,
|
45
|
+
:compile => :render,
|
46
|
+
:render => %w{RDiscount Redcarpet}
|
47
|
+
)
|
48
|
+
|
49
|
+
map(:sass,
|
50
|
+
:default => :compile,
|
51
|
+
:compile => 'Sass'
|
52
|
+
)
|
53
|
+
|
54
|
+
end
|
32
55
|
|
data/lib/shift/string.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Shift
|
5
|
+
|
6
|
+
# Mixin for things that should be transformable by Shift.
|
7
|
+
# Classes using the mixin should implement two methods:
|
8
|
+
#
|
9
|
+
# - `#data` the data to be processed
|
10
|
+
# - `#name` the file or extension name of the data
|
11
|
+
#
|
12
|
+
# This is needed for the mappings to work, even if the type
|
13
|
+
# is not a string, and/or is not persisted to disk. In this
|
14
|
+
# respect, the name is actually the name of the type.
|
15
|
+
#
|
16
|
+
module Transformable
|
17
|
+
|
18
|
+
attr_accessor :name
|
19
|
+
alias :path :name
|
20
|
+
alias :path= :name=
|
21
|
+
alias :format :name
|
22
|
+
alias :format= :name=
|
23
|
+
|
24
|
+
# Get the default interface class for this transformable.
|
25
|
+
#
|
26
|
+
# @param [Symbol] action The action to perform.
|
27
|
+
# Sample: `:compress`
|
28
|
+
# @param [String] name_override Use the specified file name or
|
29
|
+
# extension rather than `#name`
|
30
|
+
# @return [Shift::Interface] The preferred available default
|
31
|
+
# interface
|
32
|
+
# @raise [UnknownFormatError] when a type is needed but cannot
|
33
|
+
# be determined.
|
34
|
+
# @raise [UnknownActionError] when no such action exists.
|
35
|
+
# @raise [DependencyError] when required interfaces are
|
36
|
+
# not available.
|
37
|
+
#
|
38
|
+
def interface(action=:default, name_override=nil)
|
39
|
+
return action if action.is_a?(Shift::Interface)
|
40
|
+
iface = Shift[name_override || name, action] || name_error
|
41
|
+
iface.new
|
42
|
+
end
|
43
|
+
def can?(*args)
|
44
|
+
begin
|
45
|
+
interface(*args)
|
46
|
+
rescue LookupError
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Process the string with one of the Shift interfaces.
|
53
|
+
# @return [Shift::String] transformed Shift string.
|
54
|
+
# (see Shift::String#interface)
|
55
|
+
#
|
56
|
+
def process(action=:default, name_override=nil)
|
57
|
+
iface = interface(action, name_override)
|
58
|
+
self.class.new(
|
59
|
+
iface.process(data),
|
60
|
+
iface.rename(name_override || name)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(*args)
|
65
|
+
can?(*args) ? process(*args) : super
|
66
|
+
end
|
67
|
+
|
68
|
+
def respond_to?(method)
|
69
|
+
super || can?(method)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def name_error
|
75
|
+
raise(UnknownFormatError, 'cannot determine format without clues.')
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
# String result from one of the operations.
|
83
|
+
# Extends string with some useful helper methods to write to
|
84
|
+
# disk, do further transformations etc, allowing chaining of
|
85
|
+
# operations.
|
86
|
+
#
|
87
|
+
class String < ::String
|
88
|
+
|
89
|
+
include Transformable
|
90
|
+
|
91
|
+
# Create a new shift string from a standard string.
|
92
|
+
# @param [String] str The source string
|
93
|
+
# @param [String] name A file path, partial path, or
|
94
|
+
# extension associated with the string.
|
95
|
+
# @return [Shift::String] A Shift string that can be
|
96
|
+
# manipulated further.
|
97
|
+
def initialize(str='', name=nil)
|
98
|
+
super(str)
|
99
|
+
@name = name
|
100
|
+
end
|
101
|
+
|
102
|
+
def data
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
# Read a file and append its contents.
|
107
|
+
#
|
108
|
+
def read_append(path)
|
109
|
+
data << File.read(path)
|
110
|
+
self
|
111
|
+
end
|
112
|
+
alias :append_read :read_append
|
113
|
+
|
114
|
+
# Write the string to a sepcified path.
|
115
|
+
# @ return self
|
116
|
+
def write(name_override=nil)
|
117
|
+
path = name_override || name || name_error
|
118
|
+
File.open(path, 'w') {|f| f.write(data) }
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
end
|
data/shift.gemspec
CHANGED
@@ -13,11 +13,12 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.version = Shift::VERSION
|
14
14
|
s.platform = Gem::Platform::RUBY
|
15
15
|
|
16
|
-
s.summary = '
|
17
|
-
s.description = 'Shift is a generic interface to different compilers, compressors,
|
16
|
+
s.summary = 'Compiler and transformer interface framework'
|
17
|
+
s.description = 'Shift is a generic interface to different compilers, compressors, and so on. You can use it to build chains, like Shift.read("cup.coffee").compile.minify.write'
|
18
18
|
|
19
19
|
s.files = `git ls-files`.split("\n")
|
20
20
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
21
21
|
s.require_path = 'lib'
|
22
22
|
s.bindir = 'bin'
|
23
|
+
s.executables = ['shifter']
|
23
24
|
end
|
data/test/data/letter.echo
CHANGED
data/test/helper.rb
CHANGED
@@ -10,28 +10,18 @@ class TestCase < MiniTest::Unit::TestCase
|
|
10
10
|
define_method(test_name, &block)
|
11
11
|
end
|
12
12
|
|
13
|
-
def with_tempfile(data=nil)
|
14
|
-
file = Tempfile.new(
|
13
|
+
def with_tempfile(data=nil,fmt='')
|
14
|
+
file = Tempfile.new("shift_test#{fmt}")
|
15
15
|
file.write(data) if data
|
16
16
|
file.close
|
17
17
|
yield(file.path)
|
18
18
|
file.unlink
|
19
19
|
end
|
20
20
|
|
21
|
-
def file(name)
|
22
|
-
File.join(File.dirname(__FILE__), 'data', name)
|
23
|
-
end
|
24
|
-
|
25
21
|
end
|
26
22
|
|
27
23
|
|
28
24
|
|
29
25
|
|
30
26
|
|
31
|
-
module Unavabelizer
|
32
|
-
def available?
|
33
|
-
false
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
27
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'interface_test')
|
2
2
|
|
3
3
|
|
4
|
-
class ClosureCompilerTest <
|
4
|
+
class ClosureCompilerTest < InterfaceTest
|
5
5
|
|
6
6
|
FUNCTION = <<-eos
|
7
7
|
function hello(name) {
|
@@ -30,4 +30,8 @@ class ClosureCompilerTest < IdentityTest
|
|
30
30
|
}
|
31
31
|
end
|
32
32
|
|
33
|
+
def name_transformations
|
34
|
+
{ 'script.js' => 'script.min.js' }
|
35
|
+
end
|
36
|
+
|
33
37
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'interface_test')
|
2
2
|
|
3
3
|
|
4
|
-
class CoffeeScriptTest <
|
4
|
+
class CoffeeScriptTest < InterfaceTest
|
5
5
|
|
6
6
|
def subject
|
7
7
|
Shift::CoffeeScript
|
@@ -20,4 +20,8 @@ class CoffeeScriptTest < IdentityTest
|
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
+
def name_transformations
|
24
|
+
{ 'cup.coffee' => 'cup.js' }
|
25
|
+
end
|
26
|
+
|
23
27
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'interface_test')
|
2
|
+
|
3
|
+
|
4
|
+
class RDiscountTest < InterfaceTest
|
5
|
+
|
6
|
+
def subject
|
7
|
+
Shift::RDiscount
|
8
|
+
end
|
9
|
+
|
10
|
+
def instance
|
11
|
+
subject.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def transformations
|
15
|
+
{ 'hello' => "<p>hello</p>\n" }
|
16
|
+
end
|
17
|
+
|
18
|
+
def name_transformations
|
19
|
+
{ 'doc.md' => 'doc.html' }
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|