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
+ 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