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,43 @@
1
+
2
+ require 'shift'
3
+
4
+
5
+ module Shift
6
+
7
+ # The `shifter` command line interface.
8
+ #
9
+ class CLI
10
+
11
+ USAGE = "Usage: shifter <file> [action] [format]\n" +
12
+ "In pipe: shifter - format [action]\n\n" +
13
+ "Available formats and actions:\n\n" +
14
+ Shift.inspect_actions
15
+
16
+ def self.new
17
+ begin
18
+ super(*ARGV)
19
+ rescue ArgumentError
20
+ abort USAGE
21
+ end
22
+ end
23
+
24
+ def initialize(path, a=nil, b=nil)
25
+ @path = path
26
+ @format, @action = stdin? ? [a,b] : [b,a]
27
+ @format ||= :echo
28
+ @action ||= :default
29
+ end
30
+
31
+ def stdin?
32
+ @path == '-'
33
+ end
34
+
35
+ def data
36
+ stdin? ? STDIN.read : File.read(@path)
37
+ end
38
+
39
+ def run!
40
+ puts Shift(data, @format).process(@action)
41
+ end
42
+ end
43
+ end
@@ -12,9 +12,13 @@ module Shift
12
12
  #
13
13
  class DependencyError < Error; end
14
14
 
15
- # Raised when you try to read a file for which there are
16
- # no mappings without specifying its type.
15
+ class LookupError < Error; end
16
+ class UnknownFormatError < LookupError; end
17
+ class UnknownActionError < LookupError; end
18
+
19
+ # Raised when encountering invalid mappings, like
20
+ # when there is a cycle in the map.
17
21
  #
18
- class UnknownFormatError < Error; end
22
+ class MappingError < Error; end
19
23
 
20
24
  end
@@ -0,0 +1,23 @@
1
+
2
+
3
+ module Shift
4
+ class ClosureCompiler < Interface
5
+
6
+ def self.gem_dependencies
7
+ %w{closure-compiler}
8
+ end
9
+
10
+ def self.engine_class
11
+ Closure::Compiler
12
+ end
13
+
14
+ def self.target_format
15
+ 'min.js'
16
+ end
17
+
18
+ def process(str)
19
+ @engine.compile(str)
20
+ end
21
+
22
+ end
23
+ end
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Shift
3
- class CoffeeScript < Identity
3
+ class CoffeeScript < Interface
4
4
 
5
5
  def self.gem_dependencies
6
6
  %w{coffee-script}
@@ -10,12 +10,15 @@ module Shift
10
10
  %w{coffee-script}
11
11
  end
12
12
 
13
+ def self.target_format
14
+ 'js'
15
+ end
16
+
13
17
  def initialize(opts={})
14
18
  @opts = opts
15
19
  end
16
20
 
17
-
18
- def process_plain(str)
21
+ def process(str)
19
22
  ::CoffeeScript.compile(str, @opts)
20
23
  end
21
24
 
@@ -0,0 +1,9 @@
1
+ module Shift
2
+ class Echo < Interface
3
+
4
+ def self.keep_extension?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -1,16 +1,20 @@
1
1
 
2
2
  module Shift
3
- class RDiscount < Identity
3
+ class RDiscount < Interface
4
4
 
5
5
  def self.gem_dependencies
6
6
  %w{rdiscount}
7
7
  end
8
8
 
9
+ def self.target_format
10
+ 'html'
11
+ end
12
+
9
13
  def initialize(*switches)
10
14
  @switches = switches
11
15
  end
12
16
 
13
- def process_plain(str)
17
+ def process(str)
14
18
  ::RDiscount.new(str, *@switches).to_html
15
19
  end
16
20
 
@@ -1,15 +1,19 @@
1
1
  module Shift
2
- class Redcarpet < Identity
2
+ class Redcarpet < Interface
3
3
 
4
4
  def self.gem_dependencies
5
5
  %w{redcarpet}
6
6
  end
7
7
 
8
+ def self.target_format
9
+ 'html'
10
+ end
11
+
8
12
  def initialize(*switches)
9
13
  @switches = switches
10
14
  end
11
15
 
12
- def process_plain(str)
16
+ def process(str)
13
17
  ::Redcarpet.new(str, *@switches).to_html
14
18
  end
15
19
 
@@ -1,15 +1,19 @@
1
1
  module Shift
2
- class Sass < Identity
2
+ class Sass < Interface
3
3
 
4
4
  def self.gem_dependencies
5
5
  %w{sass}
6
6
  end
7
7
 
8
+ def self.target_format
9
+ 'css'
10
+ end
11
+
8
12
  def initialize(opts={})
9
13
  @opts = opts
10
14
  end
11
15
 
12
- def process_plain(str)
16
+ def process(str)
13
17
  ::Sass::Engine.new(str, @opts).render
14
18
  end
15
19
 
@@ -1,17 +1,21 @@
1
1
 
2
2
 
3
3
  module Shift
4
- class UglifyJS < Identity
4
+ class UglifyJS < Interface
5
5
 
6
6
  def self.gem_dependencies
7
7
  %w{uglifier}
8
8
  end
9
9
 
10
- def self.compiler_class
10
+ def self.engine_class
11
11
  Uglifier
12
12
  end
13
13
 
14
- def process_plain(str)
14
+ def self.target_format
15
+ 'min.js'
16
+ end
17
+
18
+ def process(str)
15
19
  @engine.compile(str)
16
20
  end
17
21
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Shift
3
- class YUICompressor < Identity
3
+ class YUICompressor < Interface
4
4
 
5
5
  def self.gem_dependencies
6
6
  %w{yui-compressor}
@@ -10,11 +10,15 @@ module Shift
10
10
  %w{yui/compressor}
11
11
  end
12
12
 
13
- def self.compiler_class
13
+ def self.engine_class
14
14
  YUI::JavaScriptCompressor
15
15
  end
16
16
 
17
- def process_plain(str)
17
+ def self.target_format
18
+ 'min.js'
19
+ end
20
+
21
+ def process(str)
18
22
  @engine.compress(str)
19
23
  end
20
24
 
@@ -0,0 +1,23 @@
1
+ module Shift
2
+ class ZlibReader < Interface
3
+
4
+ def self.keep_extension?
5
+ false
6
+ end
7
+
8
+ def self.target_format
9
+ false
10
+ end
11
+
12
+ def initialize
13
+ require 'zlib'
14
+ require 'stringio'
15
+ end
16
+
17
+ def process(data)
18
+ gz = Zlib::GzipReader.new(StringIO.new(data))
19
+ gz.read
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module Shift
2
+ class ZlibWriter < Interface
3
+
4
+ def self.keep_extension?
5
+ true
6
+ end
7
+
8
+ def self.target_format
9
+ 'gz'
10
+ end
11
+
12
+ def initialize
13
+ require 'zlib'
14
+ require 'stringio'
15
+ end
16
+
17
+ def process(str)
18
+ StringIO.open('', 'w') do |io|
19
+ gz = Zlib::GzipWriter.new(io)
20
+ gz.write(str)
21
+ gz.close
22
+ io.string
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -1,28 +1,13 @@
1
1
 
2
-
3
2
  module Shift
4
3
 
5
- # Returns the input unaltered. The purpose is mainly to inherit
6
- # from this when defining other, more useful compilers.
4
+ # The default Shift interface, from which other interfaces must
5
+ # inherit. Also works as an identity function or echo server, in
6
+ # that it echoes what it is given.
7
7
  #
8
- class Identity
9
-
10
- # Mixed into the resulting strings to make them easy to save.
11
- #
12
- module StringExtension
13
-
14
- # Write the string to a sepcified path.
15
- #
16
- def write(path)
17
- File.open(path, 'w') do |file|
18
- file.write(self)
19
- end
20
- self
21
- end
22
- end
23
-
8
+ class Interface
24
9
 
25
- # One-liner on what the user must have/do to make it available.
10
+ # One-liner on what the user can do to make it available.
26
11
  # Used in DependencyError.
27
12
  #
28
13
  def self.instructions
@@ -41,7 +26,7 @@ module Shift
41
26
  gem_dependencies.all? {|d| Gem.available?(d) }
42
27
  end
43
28
 
44
- # A list of Rubygems needed for the component to work.
29
+ # A list of Rubygems needed for the interface to work.
45
30
  #
46
31
  def self.gem_dependencies
47
32
  []
@@ -53,10 +38,23 @@ module Shift
53
38
  gem_dependencies
54
39
  end
55
40
 
56
- # The class of the wrapped compiler, or false if none
41
+ # The class of the wrapped generator, or false if none
57
42
  # is used.
58
43
  #
59
- def self.compiler_class
44
+ def self.engine_class
45
+ false
46
+ end
47
+
48
+ # The format typically produced by the generator.
49
+ #
50
+ def self.target_format
51
+ false
52
+ end
53
+
54
+ # Whether to leave the old extension as is and append, like
55
+ # something.css => something.css.gzip
56
+ #
57
+ def self.keep_extension?
60
58
  false
61
59
  end
62
60
 
@@ -75,42 +73,41 @@ module Shift
75
73
  super
76
74
  end
77
75
 
78
- # Create a new instance. Ignores the given options.
76
+ # Create a new instance with the given options.
79
77
  #
80
78
  def initialize(*prms)
81
- if self.class.compiler_class
82
- @engine = self.class.compiler_class.new(*prms)
79
+ if self.class.engine_class
80
+ @engine = self.class.engine_class.new(*prms)
83
81
  end
84
82
  end
85
83
 
86
- # Process the supplied string, returning the resulting `String` (with a #write method attached to it).
84
+ # Process the supplied string, returning the resulting `String`.
87
85
  #
88
86
  def process(str)
89
- process_plain(str).extend(StringExtension)
87
+ str.dup
90
88
  end
91
89
  alias :compress :process
92
90
  alias :compile :process
93
91
  alias :transform :process
94
92
 
95
- # Process the supplied string, returning the resulting `String`.
93
+ # Get the default filename of a transformed file.
94
+ # @param [String] file Path of the original file
95
+ # @return [String] default file name for the result.
96
96
  #
97
- def process_plain(str)
98
- str.dup
99
- end
97
+ def rename(file)
98
+ return nil if file.nil?
100
99
 
101
- # Read and process a file.
102
- #
103
- # @raise [DependencyError] when none of the mapped
104
- # implementations are available.
105
- #
106
- # @return [String] The processed `String`
107
- #
108
- def read(path)
109
- process(File.read(path)).extend(StringExtension)
100
+ unless self.class.keep_extension?
101
+ file = file.is_a?(Symbol) ?
102
+ file.to_s : file.chomp(File.extname(file))
103
+ end
104
+ if self.class.target_format
105
+ file = file + '.' + self.class.target_format.to_s
106
+ end
107
+ file
110
108
  end
111
109
 
112
110
  end
113
- Echo = Identity
114
111
  end
115
112
 
116
113
 
@@ -0,0 +1,67 @@
1
+ module Shift
2
+ class InterfaceList
3
+
4
+ # Create a new InterfaceList, given an array of class names
5
+ # of shift interfaces. Immutable once created.
6
+ #
7
+ def initialize(ifaces)
8
+ @interfaces = ifaces
9
+ end
10
+
11
+ def to_hash
12
+ @interfaces.dup
13
+ end
14
+
15
+ def eql?(other)
16
+ @interfaces.eql? other.to_hash
17
+ end
18
+ alias :== :eql?
19
+
20
+ def hash
21
+ @interfaces.hash
22
+ end
23
+
24
+ def inspect
25
+ 'InterfaceList' + @interfaces.inspect
26
+ end
27
+
28
+ # Pick the preferred available interface.
29
+ #
30
+ # @raise [DependencyError] when none of the interfaces
31
+ # are available.
32
+ #
33
+ # @return [Class] The preferred available class
34
+ #
35
+ def pick
36
+ each_class {|kls| return kls if kls.available? }
37
+ raise DependencyError, "nothing available: " +
38
+ help_string
39
+ end
40
+
41
+ private
42
+
43
+ def help_string
44
+ if @interfaces.any?
45
+ @interfaces.inspect + " Possible solution: " +
46
+ first_class.instructions
47
+ else
48
+ 'no interfaces mapped'
49
+ end
50
+ end
51
+
52
+ def each_class
53
+ @interfaces.each do |name|
54
+ yield get_iface(name)
55
+ end; nil
56
+ end
57
+
58
+ def first_class
59
+ get_iface(@interfaces.first)
60
+ end
61
+
62
+ def get_iface(name)
63
+ Shift.const_get(name)
64
+ end
65
+
66
+ end
67
+ end