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.
Files changed (48) hide show
  1. data/CHANGELOG +20 -0
  2. data/README.md +105 -37
  3. data/Rakefile +1 -1
  4. data/TODO +3 -0
  5. data/bin/shifter +5 -0
  6. data/lib/shift.rb +42 -36
  7. data/lib/shift/action_map.rb +106 -0
  8. data/lib/shift/cli.rb +43 -0
  9. data/lib/shift/errors.rb +7 -3
  10. data/lib/shift/i/closure_compiler.rb +23 -0
  11. data/lib/shift/{c → i}/coffee_script.rb +6 -3
  12. data/lib/shift/i/echo.rb +9 -0
  13. data/lib/shift/{c → i}/rdiscount.rb +6 -2
  14. data/lib/shift/{c → i}/redcarpet.rb +6 -2
  15. data/lib/shift/{c → i}/sass.rb +6 -2
  16. data/lib/shift/{c → i}/uglify_js.rb +7 -3
  17. data/lib/shift/{c → i}/yui_compressor.rb +7 -3
  18. data/lib/shift/i/zlib_reader.rb +23 -0
  19. data/lib/shift/i/zlib_writer.rb +27 -0
  20. data/lib/shift/{c/identity.rb → interface.rb} +39 -42
  21. data/lib/shift/interface_list.rb +67 -0
  22. data/lib/shift/interfaces.rb +20 -0
  23. data/lib/shift/mapper.rb +95 -0
  24. data/lib/shift/mappings.rb +51 -28
  25. data/lib/shift/mappings.yml +6 -0
  26. data/lib/shift/string.rb +125 -0
  27. data/shift.gemspec +3 -2
  28. data/test/data/letter.echo +0 -5
  29. data/test/helper.rb +2 -12
  30. data/test/{c → i}/closure_compiler_test.rb +6 -2
  31. data/test/{c → i}/coffee_script_test.rb +6 -2
  32. data/test/i/rdiscount_test.rb +22 -0
  33. data/test/i/redcarpet_test.rb +22 -0
  34. data/test/i/sass_test.rb +19 -0
  35. data/test/{c → i}/uglify_js_test.rb +6 -2
  36. data/test/{c → i}/yui_compressor_test.rb +6 -2
  37. data/test/i/zlib_reader.rb +24 -0
  38. data/test/interface_test.rb +60 -0
  39. data/test/mapper_test.rb +144 -0
  40. data/test/shift_test.rb +12 -43
  41. data/test/string_test.rb +39 -0
  42. metadata +39 -24
  43. data/lib/shift/c/closure_compiler.rb +0 -19
  44. data/test/c/identity_test.rb +0 -79
  45. data/test/c/rdiscount_test.rb +0 -18
  46. data/test/c/redcarpet_test.rb +0 -18
  47. data/test/c/sass_test.rb +0 -14
  48. 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
@@ -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
@@ -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
 
@@ -0,0 +1,6 @@
1
+
2
+ shift.global:
3
+ gzip: ZlibCompressor
4
+
5
+ echo:
6
+ default: :echo
@@ -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
@@ -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 = 'Shift is a generic interface to different compilers, compressors, etc.'
17
- s.description = 'Shift is a generic interface to different compilers, compressors, etc. What the Tilt gem does for template languages, Shift does for compilers and 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
@@ -1,6 +1 @@
1
1
 
2
- Hello dear.
3
-
4
- Could you please bring with you my electric power drill, a cup of baking soda, three rolls of duct tape and The Brothers Karamazov when you come back from the apartment? I'm going on a mission again.
5
-
6
- Thanks.
@@ -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('shift_test')
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__), 'identity_test')
1
+ require File.join(File.dirname(__FILE__), '..', 'interface_test')
2
2
 
3
3
 
4
- class ClosureCompilerTest < IdentityTest
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__), 'identity_test')
1
+ require File.join(File.dirname(__FILE__), '..', 'interface_test')
2
2
 
3
3
 
4
- class CoffeeScriptTest < IdentityTest
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