shift 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ HEAD
4
+ Interface defaults
5
+
6
+ 0.4.0
7
+ Major restructuring
8
+ Shift::String for method chaning
9
+ Gzip support
10
+
11
+ 0.3.0
12
+ Shell command
13
+
14
+ 0.2.0
15
+ Shift.inspect_mappings
16
+ Named actions
17
+
18
+ 0.1.0
19
+ Working Rubygem with support for some formats.
20
+
data/README.md CHANGED
@@ -1,99 +1,167 @@
1
1
 
2
- Shift (draft)
2
+ Shift
3
3
  =====
4
4
 
5
- **State and logic-less compiler and compressor interface**
5
+ **Compiler and compressor interface**
6
6
 
7
7
  ---
8
8
 
9
- Shift is a generic Ruby interface to different compilers, compressors, transformers and so on. What the [Tilt](https://github.com/rtomayko/tilt) gem does for template languages, Shift does for stuff that compiles in one step, without stateful template logic.
9
+ Shift is a generic Ruby interface to different compilers, compressors, transformers and so on. It is also an easy way to chain actions that transform something and build compiler chains.
10
10
 
11
- * [Documentation](http://rubydoc.info/github/jbe/shift/master/frames)
12
- * [File type mappings](http://rubydoc.info/github/jbe/shift/master/Shift)
11
+ * Installation: `gem install shift`
12
+ * API Documentation
13
+ * [Latest Gem](http://rubydoc.info/gems/shift/frames)
14
+ * [Github master](http://rubydoc.info/github/jbe/shift/master/frames)
15
+ * [Default mappings](https://github.com/jbe/shift/blob/master/lib/shift/mappings.rb)
13
16
 
14
- ### Installation
17
+ Shift contains:
15
18
 
16
- gem install tilt
19
+ * A convention for compiler interfaces
20
+ * A collection of such interfaces for common compilers, compressors etc.
21
+ * A way to describe actions that can be performed on a given format (such as `:render` for `markdown` files)
22
+ * A way to map such format actions to lists of compilers performing that action
23
+ * A way to return the best available compiler from such a list
24
+ * Default mappings for all of the above
17
25
 
18
26
 
19
- ### Usage
20
-
21
- To read and process a file, using the preferred available default component for that filetype:
27
+ This means you can do things like
22
28
 
23
29
  ```ruby
24
30
 
25
- Shift.read('thefile.js') # => minified js string
31
+ (Shift.read('cup.coffee').compile.compress << '/* pidgeon */'
32
+ ).gzip.write
26
33
 
27
34
  ```
28
35
 
29
- Or to read, process, and then write:
36
+ This reads `cup.coffee`, compiles it using coffeescript, compresses it using UglifyJS, appends the pidgeon comment to the minified javascript, gzips all of that, and then saves it as `cup.min.js.gz`. Since no file name was given, it is determined automatically.
37
+
38
+ The aim is to include a large library of interfaces, which are lazy loaded on demand, to allow a huge variety of operations.
39
+
40
+
41
+ ### Examples
30
42
 
31
43
  ```ruby
32
44
 
33
- Shift.read('canopy.sass').write('canopy.css')
45
+ str = Shift("hello", "hi.markdown") # => "hello"
46
+ str.class # => Shift::String
47
+ str.name # => "hi.markdown"
48
+
49
+ result = str.render # => "<p>hello</p>"
50
+ result.name # => "hi.html"
34
51
 
35
52
  ```
36
53
 
37
- The components can also be used directly:
54
+ The `Shift::String` works like a normal string, except that it has an associated name, and that it can be transformed like we just did. The string name is theoretical only; it does not necessarily exist as a file. Therefore, it can also simply represent the format. There are methods to read and write from file paths however:
38
55
 
39
56
  ```ruby
40
57
 
41
- cc = Shift::ClosureCompiler.new(:compilation_level => 'ADVANCED_OPTIMIZATIONS')
42
- minified_js_string = cc.read(path)
58
+ str_a = Shift.read('script.js') # => "var hello; hello = 31;"
59
+ str_a.class # => Shift::String
60
+
61
+ str_b = Shift('hello', 'message.txt')
62
+ str_b.write
63
+ str_b.write('message_copy.txt')
43
64
 
44
65
  ```
45
66
 
46
- To simply process a string, or to process and save a string:
67
+ Pretty handy. It can automatically write the string to the path that it automatically figured out.
68
+
69
+ Now, in the first example, how did it know how to render markdown? Because i have RDiscount installed and it is the preferred default interface for doing `:render` to `.markdown` files.
47
70
 
48
71
  ```ruby
49
72
 
50
- md = Shift::RDiscount.new
51
- md.process("hello *there*") # => "<p>hello <em>there</em></p>"
52
- md.process("hello *there*").write('message.html')
73
+ str = Shift("hello", "hi.markdown") # => "hello"
74
+ str.render # => "<p>hello</p>"
75
+ str.interface # => #<Shift::RDiscount:0xa0ad818>
76
+
77
+ Shift[:markdown] # => Shift::RDiscount
78
+ Shift[:md] # => Shift::RDiscount
79
+ Shift['somefile.js'] # => Shift::UglifyJS
80
+
81
+ Shift[:js, :compress] # => Shift::UglifyJS
82
+ Shift[:js, :gzip] # => Shift::ZlibCompressor
83
+
84
+ puts Shift.inspect_actions
85
+ # =>
86
+ # GLOBAL: gzip
87
+ # echo: default
88
+ # coffee: compile
89
+ # gzip, gz: inflate
90
+ # js: compress
91
+ # md, markdown: render
92
+ # sass: compile
53
93
 
54
94
  ```
55
- To see if a component is available (check if the gem is installed and so on):
95
+ If i tried to look up a format, and it had mappings, but none of the underlying handlers were available, Shift would raise a `Shift::DependencyError` including installation instructions.
96
+
97
+ What if i want to work with the interfaces without any magic?
56
98
 
57
99
  ```ruby
58
100
 
59
- Shift::YUICompressor.available?.
101
+ Shift::ClosureCompiler.available? # => true
102
+
103
+ iface = Shift::ClosureCompiler.new(
104
+ :compilation_level => 'ADVANCED_OPTIMIZATIONS')
105
+ iface.process(str)
106
+ iface.rename(file_path)
60
107
 
61
108
  ```
62
109
 
63
- To get the first available preferred default component class for a file:
110
+ Being interfaces, they all work the same way.
111
+
112
+
113
+ ### Defining new mappings
64
114
 
65
115
  ```ruby
66
116
 
67
- Shift['somefile.js'] # => Shift::UglifyJS
117
+ Shift.map('myformat', 'myaliasformat',
118
+ :default => :crush,
119
+ :crush => 'MyFormatCrusher',
120
+ :stabilize => %w{MyFormatStabilizer AlternativeMyFormatStabilizer}
121
+ )
68
122
 
69
123
  ```
70
124
 
71
- You can also do:
125
+ Or globally, for all formats:
72
126
 
73
127
  ```ruby
128
+ Shift.global.map(
129
+ :mix_up => 'Mixuper'
130
+ )
131
+ ```
74
132
 
75
- Shift[:md] # => Shift::RDiscount
133
+ To reset all mappings:
134
+
135
+ ```ruby
136
+
137
+ Shift::MAP = {}
76
138
 
77
139
  ```
78
140
 
79
- ### Available engines
141
+ * [Default mappings](https://github.com/jbe/shift/blob/master/lib/shift/mappings.rb)
142
+
143
+
144
+ ### Shifter command line tool
80
145
 
81
- * UglifyJS
82
- * ClosureCompiler
83
- * YUICompressor
84
- * CoffeeScript
85
- * Sass
86
- * RDiscount
87
- * Redcarpet
146
+ There is also a command line tool called `shifter`.
88
147
 
148
+ ```bash
89
149
 
90
- ### Why not use or extend Tilt instead?
150
+ shifter
151
+
152
+ shifter sheet.sass
153
+ shifter file.js compress
154
+ shifter style.s compile sass
155
+
156
+ some-tool | shifter - js > myfile.min.js
157
+ some-tool | shifter - js compress > myfile.min.js
158
+
159
+ ```
91
160
 
92
- I am making a separate library for this rather than extending Tilt, because i would usually only need one of the two in a given context. One and two step compilation are somewhat different things. Shift is more on the build side. Tilt is more on the dynamic side.
93
161
 
94
162
  ### Bye
95
163
 
96
- Bye Bye, see you. There are proper [docs](http://rubydoc.info/github/jbe/shift/master/frames) if you want more. And once again, [the mappings are there too](http://rubydoc.info/github/jbe/shift/master/Shift). Contributions and feedback is welcome, of course.
164
+ Bye Bye, see you. Contribute some interfaces and mappings if you want to.
97
165
 
98
166
  ---
99
167
 
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ task :test do
8
8
  end
9
9
 
10
10
  task :shell do
11
- system 'pry -I lib -r shift'
11
+ system 'pry -I lib -r shift --trace'
12
12
  end
13
13
 
14
14
  task :default => :test
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+
2
+ - Support IO streams?
3
+ - Shift::Array which delegates methods to its members
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'shift/cli'
4
+
5
+ Shift::CLI.new.run!
@@ -2,59 +2,65 @@
2
2
 
3
3
  # SHIFT
4
4
  #
5
- # State and logic-less compiler and compressor interface
5
+ # Compiler and compressor interface framework
6
6
  #
7
7
  # (c) 2011 Jostein Berre Eliassen - MIT licence
8
8
 
9
9
 
10
10
  module Shift
11
11
 
12
- VERSION = '0.1.0'
12
+ VERSION = '0.4.0'
13
13
 
14
14
  require 'shift/errors'
15
+ require 'shift/mapper'
16
+ require 'shift/interfaces'
15
17
  require 'shift/mappings'
16
18
 
19
+ autoload :String, 'shift/string'
17
20
 
18
- # components
19
-
20
- autoload :Identity, 'shift/c/identity'
21
- autoload :UglifyJS, 'shift/c/uglify_js'
22
- autoload :ClosureCompiler, 'shift/c/closure_compiler'
23
- autoload :YUICompressor, 'shift/c/yui_compressor'
24
- autoload :CoffeeScript, 'shift/c/coffee_script'
25
- autoload :Sass, 'shift/c/sass'
26
- autoload :RDiscount, 'shift/c/rdiscount'
27
- autoload :Redcarpet, 'shift/c/redcarpet'
28
- autoload :RedCarpet, 'shift/c/redcarpet'
29
21
 
22
+ class << self
23
+
24
+ # (see Shift::String.new)
25
+ #
26
+ def new(*args)
27
+ Shift::String.new(*args)
28
+ end
30
29
 
31
- # Read and process a file with the mapped component.
32
- #
33
- # @see Shift.[]
34
- #
35
- # (see Identity#read)
36
- #
37
- def self.read(path, opts={})
38
- self[path].new(opts).read(path)
39
- end
30
+ # Read a file, returning a Shift string.
31
+ # @param [String] path the file to read from.
32
+ # @param [String] new_path treat the shift string as if it came
33
+ # from this path. Can be used to override file type behavior.
34
+ #
35
+ # (see Shift.new)
36
+ #
37
+ def read(path, new_path=nil)
38
+ new(File.read(path), new_path || path)
39
+ end
40
40
 
41
- # Get the preferred available class mapped to match the
42
- # given filename or extension.
43
- #
44
- # @raise [UnknownFormatError] when none of the mappings match.
45
- #
46
- # (see Shift.best_available_mapping_for)
47
- #
48
- def self.[](file)
49
- pattern = File.basename(file.to_s.downcase)
50
- until pattern.empty?
51
- if MAPPINGS[pattern]
52
- return best_available_mapping_for(pattern)
41
+ # Read and concatenate several files.
42
+ # (see Shift.read)
43
+ #
44
+ # TODO: glob
45
+ #
46
+ def concat(*globs)
47
+ buf = new('', File.extname(globs.first))
48
+ globs.each do |glob|
49
+ Dir[glob].each do |file|
50
+ buf.read_append(file)
51
+ end
53
52
  end
54
- pattern.sub!(/^[^.]*\.?/, '')
53
+ buf
55
54
  end
56
- raise UnknownFormatError, "no mapping matches #{file}"
55
+ alias :cat :concat
56
+
57
57
  end
58
+ end
58
59
 
60
+ # (see Shift.new)
61
+ #
62
+ def Shift(*args)
63
+ Shift.new(*args)
59
64
  end
60
65
 
66
+
@@ -0,0 +1,106 @@
1
+
2
+ module Shift
3
+
4
+ # A mapping of actions to interfaces. Handles validation,
5
+ # lookup, updates, link walking etc.
6
+ #
7
+ class ActionMap
8
+
9
+ def initialize(fallback, actions={})
10
+ @actions = {}
11
+ @fallback = fallback
12
+ map(actions)
13
+ end
14
+
15
+ attr_reader :fallback
16
+
17
+ # Return a duplicate ActionHash without fallback,
18
+ # to be used for local queries.
19
+ def local
20
+ self.class.new(nil).map(@actions)
21
+ end
22
+
23
+ def map(actions)
24
+ begin
25
+ original = @actions.dup
26
+ parse(actions)
27
+ validate
28
+ rescue Shift::Error
29
+ @actions = original
30
+ raise
31
+ end; self
32
+ end
33
+
34
+ def to_hash(inherit=true)
35
+ (inherit && @fallback) ?
36
+ @actions.merge(@fallback) : @actions.dup
37
+ end
38
+
39
+ # Return a hash of mappings that are not links
40
+ def atoms(inherit=false)
41
+ to_hash(inherit).delete_if {|k,v| v.is_a?(Symbol) }
42
+ end
43
+
44
+ def eql?(other)
45
+ (eql_id.eql? other.eql_id) &&
46
+ (@fallback.eql? other.fallback)
47
+ end
48
+ alias :== :eql?
49
+
50
+ def eql_id
51
+ [to_hash(false), @fallback]
52
+ end
53
+ def hash; eql_id.hash; end
54
+
55
+ # Look up an action
56
+ def [](action)
57
+ action = action.to_sym
58
+ item = @actions[action] || (@fallback[action] if @fallback)
59
+ item.is_a?(Symbol) ? self[item] : item
60
+ end
61
+
62
+ def inspect
63
+ 'ActionMap' + @actions.inspect
64
+ end
65
+
66
+ def dup
67
+ self.class.new(fallback ? @fallback.dup : @fallback, @actions)
68
+ end
69
+
70
+ private
71
+
72
+ def parse(hsh)
73
+ hsh = {:default => hsh} unless hsh.is_a?(Hash)
74
+
75
+ hsh.each do |name, handler|
76
+ h = case handler
77
+ when Symbol, InterfaceList then handler
78
+ when Array then handler.map {|cls| cls.to_s }
79
+ else [handler.to_s]
80
+ end
81
+ @actions[name] = h.is_a?(Array) ? InterfaceList.new(h) : h
82
+ end
83
+ end
84
+
85
+
86
+ def validate
87
+ @actions.each do |name, handler|
88
+ cycle_search(handler) if handler.is_a?(Symbol)
89
+ end
90
+ end
91
+
92
+ def cycle_search(action, visited=[])
93
+ visited << action
94
+ item = @actions[action]
95
+
96
+ raise(MappingError, "bad link #{action.inspect}") unless item
97
+
98
+ if visited.include?(item)
99
+ raise MappingError, 'cycle detected ' + visited.inspect
100
+ end
101
+
102
+ cycle_search(item, visited) if item.is_a?(Symbol)
103
+ end
104
+
105
+ end
106
+ end