orange 0.0.2

This diff has not been reviewed by any users.
Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README.markdown +154 -0
  2. data/lib/orange.rb +7 -0
  3. data/lib/orange/application.rb +125 -0
  4. data/lib/orange/carton.rb +114 -0
  5. data/lib/orange/cartons/site_carton.rb +9 -0
  6. data/lib/orange/core.rb +197 -0
  7. data/lib/orange/magick.rb +91 -0
  8. data/lib/orange/middleware/access_control.rb +100 -0
  9. data/lib/orange/middleware/base.rb +41 -0
  10. data/lib/orange/middleware/database.rb +22 -0
  11. data/lib/orange/middleware/globals.rb +18 -0
  12. data/lib/orange/middleware/recapture.rb +19 -0
  13. data/lib/orange/middleware/rerouter.rb +13 -0
  14. data/lib/orange/middleware/restful_router.rb +59 -0
  15. data/lib/orange/middleware/route_context.rb +39 -0
  16. data/lib/orange/middleware/route_site.rb +51 -0
  17. data/lib/orange/middleware/show_exceptions.rb +78 -0
  18. data/lib/orange/middleware/site_load.rb +33 -0
  19. data/lib/orange/middleware/static.rb +81 -0
  20. data/lib/orange/middleware/static_file.rb +32 -0
  21. data/lib/orange/middleware/template.rb +61 -0
  22. data/lib/orange/model_resource.rb +170 -0
  23. data/lib/orange/packet.rb +88 -0
  24. data/lib/orange/resource.rb +37 -0
  25. data/lib/orange/resources/flex_router.rb +13 -0
  26. data/lib/orange/resources/mapper.rb +55 -0
  27. data/lib/orange/resources/page_parts.rb +54 -0
  28. data/lib/orange/resources/parser.rb +60 -0
  29. data/lib/orange/routable_resource.rb +16 -0
  30. data/lib/orange/stack.rb +201 -0
  31. data/spec/application_spec.rb +146 -0
  32. data/spec/carton_spec.rb +5 -0
  33. data/spec/core_spec.rb +231 -0
  34. data/spec/magick_spec.rb +89 -0
  35. data/spec/mock/mock_app.rb +17 -0
  36. data/spec/mock/mock_core.rb +2 -0
  37. data/spec/mock/mock_middleware.rb +13 -0
  38. data/spec/mock/mock_mixins.rb +19 -0
  39. data/spec/mock/mock_pulp.rb +24 -0
  40. data/spec/mock/mock_resource.rb +5 -0
  41. data/spec/mock/mock_router.rb +10 -0
  42. data/spec/model_resource_spec.rb +5 -0
  43. data/spec/orange_spec.rb +19 -0
  44. data/spec/packet_spec.rb +134 -0
  45. data/spec/resource_spec.rb +5 -0
  46. data/spec/resources/flex_router_spec.rb +5 -0
  47. data/spec/resources/mapper_spec.rb +5 -0
  48. data/spec/resources/parser_spec.rb +5 -0
  49. data/spec/routable_resource_spec.rb +5 -0
  50. data/spec/spec_helper.rb +16 -0
  51. data/spec/stack_spec.rb +202 -0
  52. data/spec/stats.rb +182 -0
  53. metadata +194 -0
@@ -0,0 +1,154 @@
1
+ Orange
2
+ ======
3
+
4
+ Orange is intended to be a middle ground between the simplicity of Sinatra
5
+ and the power of Rails. Orange is being developed by Orange Sparkle Ball, inc
6
+ for our own use. Our main focus is on creating a super-extensible CMS
7
+ with Orange, but we're trying to make the components as reusable as possible. Our
8
+ intention is to be ready to use Orange for most client website builds by
9
+ March 2010.
10
+
11
+ **Note**: Orange is still in the alpha stage. Test coverage is lack-luster at best.
12
+ Tread carefully.
13
+
14
+ A (Theoretical) Example of Orange
15
+ =================================
16
+
17
+ _This doesn't actually work quite yet, but it's the goal we're working toward._
18
+
19
+ After installing the orange gem, create an 'app.rb'
20
+
21
+ **app.rb:**
22
+
23
+ require 'rubygems'
24
+ require 'orange'
25
+ class App < Orange::Application
26
+ end
27
+
28
+ You now have an Orange CMS that can be made by calling "App.app".
29
+ Put this line in your rackup file...
30
+
31
+ **config.ru:**
32
+
33
+ require 'app'
34
+ run App.app
35
+
36
+ Run rack however you run rack.
37
+
38
+ Look at that, a full fledged CMS in 6 lines! Not so impressive, it's all prebuilt,
39
+ right? The real question is how hard is it to customize?
40
+
41
+ I want my pages to have more than just titles and bodies. I want sidebars...
42
+
43
+ **app.rb:**
44
+
45
+ require 'rubygems'
46
+ require 'orange'
47
+ class App < Orange::Application
48
+ end
49
+ class Orange::Page
50
+ markdown :sidebar, :context => [:front]
51
+ end
52
+
53
+ We now have a sidebar that anybody can see. The backend scaffolding will adapt to allow
54
+ editing, and the front end will print it out for each page. Slap some CSS on it to make it
55
+ look like a sidebar, and tada!
56
+
57
+ Pages now have sidebars, in three lines of code and some
58
+ styling. No migrations (we rely on DataMapper's auto_upgrade functionality), no extra
59
+ files (unless we want them).
60
+
61
+ More Info
62
+ =========
63
+
64
+ Orange Philosophy
65
+ -----------------
66
+ The Orange application framework is intended to be a fully customizable CMS
67
+ capable of hosting multiple sites while maintaining Sinatra-like ease of
68
+ programming. Some core ideas behind Orange:
69
+
70
+ * Scaffolding doesn't have to be replaced if it's smart enough (most of the time)
71
+ * Put as much functionality into middleware as possible, so it can be easily reused
72
+ and remixed
73
+ * Give middleware a little more power so it's useful enough to handle more tasks
74
+
75
+
76
+ Should I Use Orange?
77
+ --------------------
78
+ Not right now, unless you want to write half the framework yourself.
79
+
80
+
81
+ When it's finished, would I want to use it?
82
+ -------------------------------------------
83
+ Depends on what you're looking for. Orange has a middleware stack intended to
84
+ be reused. If the stack has something you'd like, you could theoretically
85
+ put the middleware stack on top of Sinatra or Rails. (This hasn't actually
86
+ been tested yet.)
87
+
88
+ The full Orange application framework is intended to run
89
+ as an easily extensible CMS. We tend to think that having lots of tests
90
+ and full MVC separation just so you can add an extra type of page to the CMS
91
+ is a bit overkill. We designed this to replace ModX in our web builds for clients.
92
+
93
+ Required Gems
94
+ -------------
95
+
96
+ Make sure githubs gems can be downloaded:
97
+
98
+ $ gem sources -a http://gems.github.com
99
+
100
+ * dm-core (+ do_[sqlite3|mysql|...] )
101
+ * dm-more
102
+ * rack
103
+ * haml
104
+ * mynyml-rack-abstract-format (github)
105
+ * ruby-openid
106
+ * rack-openid
107
+ * meekish-openid_dm_store
108
+
109
+ Also, you'll need a web server of some kind and need to set it up for rack.
110
+
111
+ **Testing**
112
+
113
+ If you want to test, you'll need the following gems:
114
+
115
+ * rspec
116
+ * rack-test
117
+
118
+ Yard is also helpful for generating API docs
119
+
120
+ The following are useful rake tasks for testing purposes:
121
+
122
+ * rake test => (same as rake spec)
123
+ * rake spec => runs rspec with color enabled and spec_helper included
124
+ * rake doc => runs yardoc (no, not really necessary)
125
+ * rake clean => clear out the temporary files not included in the repo
126
+ * rake rcov => runs rspec with rcov
127
+
128
+ Programming Info
129
+ ================
130
+
131
+ The basics of using the orange framework...
132
+
133
+ Terminology
134
+ -----------
135
+
136
+ * **Application**: The last stop for the packet after traversing through the middleware stack.
137
+ * **Core**: This is the core orange object, accessible from all points of the orange
138
+ system. Usually the orange instance can be called by simply using the "orange" function
139
+ * **Mixins**: Extra functionality added directly to the core. Mixins are generally for only
140
+ a couple of extra methods, anything more should probably be created as a resource.
141
+ * **Packet**: This object represents a web request coming in to the orange system.
142
+ Each request is instantiated as a packet before it is sent through the middleware stack.
143
+ * **Pulp**: Mixin added to the packet object rather than the Core.
144
+ * **Resources**: Resources are extra functionality contained within an object, accessible
145
+ from the core.
146
+ * **Stack**: The bundled collection of Orange-enhanced middleware sitting on top of the
147
+ Orange application
148
+
149
+ Pulp and Mixins
150
+ ---------------
151
+ The ability to add pulp and mixins is incredibly handy because the packet and the core are
152
+ available from just about anywhere in the Orange framework. For instance, the haml parser
153
+ evaluates all local calls as if made to the packet, so adding pulp is essentially adding
154
+ functionality that is directly available to haml.
@@ -0,0 +1,7 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ Dir.glob(File.join(libdir, 'orange', '*.rb')).each {|f| require f }
5
+ Dir.glob(File.join(libdir, 'orange', 'cartons', '*.rb')).each {|f| require f }
6
+ Dir.glob(File.join(libdir, 'orange', 'middleware', '*.rb')).each {|f| require f }
7
+ Dir.glob(File.join(libdir, 'orange', 'resources', '*.rb')).each {|f| require f }
@@ -0,0 +1,125 @@
1
+ module Orange
2
+ # Orange::Application is the main class used for building an
3
+ # Orange App. Typically you will not initialize it directly,
4
+ # but use the app method, which returns the entire Orange
5
+ # stack, with all middleware and the Orange::Application as
6
+ # the main receiver
7
+ #
8
+ # To override the stack generated by default, you can use
9
+ # the self.stack method, which will be used to create a new
10
+ # Orange::Stack
11
+ class Application
12
+ # Initialize will set the core, and additionally accept any
13
+ # other options to be added in to the opts array
14
+ # @param [Orange::Core] core the orange core instance that this application will use
15
+ # @param [Hash] *opts the optional arguments
16
+ def initialize(core = false, *opts, &block)
17
+ @core = core
18
+ @options ||= {}
19
+ @options = Orange::Options.new(*opts, &block).hash.with_defaults(self.class.opts)
20
+ orange.register(:stack_loaded) do |s|
21
+ stack_init
22
+ end
23
+ init
24
+ end
25
+
26
+ # stack_init is ONLY called after the {#app}
27
+ # method is called, loading a stack
28
+ # (just in case the middleware stack added necessary functionality, etc)
29
+ def stack_init
30
+ end
31
+
32
+ # This method is called by initialize, subclasses should override this method
33
+ # for their own initialization needs.
34
+ #
35
+ # It will usually be better to use stack_init, which gives full access to
36
+ # the initialized stack
37
+ def init
38
+ end
39
+
40
+ # Set the orange core to be a new core
41
+ #
42
+ # Generally, the core should be set during initialization, rather
43
+ # than with this method.
44
+ # @param [Orange::Core] core the orange core instance
45
+ def set_core(core)
46
+ @core = core
47
+ end
48
+
49
+ # The standard call as required by rack. This will
50
+ # make an Orange::Packet object (if necessary) and
51
+ # then send it to the appropriate router for routing.
52
+ #
53
+ # If the :self_routing option is true (default) then
54
+ # the packet will be routed by the application if there
55
+ # is not already another class volunteering for that role.
56
+ # (Routers declare themselves in the orange
57
+ # env['route.router'] to be called by the application)
58
+ #
59
+ #
60
+ def call(env)
61
+ packet = Orange::Packet.new(@core, env)
62
+ # Set up this application as router if nothing else has
63
+ # assumed routing responsibility (for Sinatra DSL like routing)
64
+ self_routing = opts[:self_routing] || true
65
+ if (!packet['route.router'] && self_routing)
66
+ packet['route.router'] = self
67
+ end
68
+ packet.route
69
+ packet.finish
70
+ end
71
+
72
+ # Returns the core
73
+ # @return [Orange::Core] the core instance set for the application
74
+ def orange
75
+ @core
76
+ end
77
+
78
+ # The default route method for the application. Must be overridden in subclasses.
79
+ #
80
+ # This method will raise a RuntimeError if not overridden.
81
+ # The intent is for the application subclass to override this method
82
+ # and use it to handle packets not routed by Stack middleware.
83
+ def route(packet)
84
+ raise 'default response from Orange::Application.route'
85
+ end
86
+
87
+ # Used to set optional values at class level. Will be merged into the options
88
+ # given at initialization time
89
+ def self.set(key, v = true)
90
+ @class_opts ||= {}
91
+ @class_opts[key] = v
92
+ end
93
+
94
+ # Gives access to class defined options.
95
+ def self.opts
96
+ @class_opts ||= {}
97
+ end
98
+
99
+ # Gives access to options for the application, both from the class and the
100
+ # instance level.
101
+ # @return [Hash] the options hash
102
+ def opts
103
+ @options
104
+ end
105
+
106
+ # Returns an instance of Orange::Stack to be run by Rack
107
+ #
108
+ # Usually, you'll call this in the rackup file: `run MyApplication.app`
109
+ def self.app
110
+ if @app.instance_of?(Proc)
111
+ Orange::Stack.new &@app # turn saved proc into a block arg
112
+ else
113
+ Orange::Stack.new self
114
+ end
115
+ end
116
+
117
+ # Changes the stack that will be used when {#app}
118
+ # is called
119
+ #
120
+ # Each call to stack overrides the previous one.
121
+ def self.stack(&block)
122
+ @app = Proc.new # pulls in the block and makes it a proc
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,114 @@
1
+ require 'dm-core'
2
+
3
+ module Orange
4
+ class Carton
5
+
6
+ def self.as_resource
7
+ name = self.to_s
8
+ eval <<-HEREDOC
9
+ class ::#{name}_Resource < Orange::ModelResource
10
+ use #{name}
11
+ end
12
+ HEREDOC
13
+ end
14
+
15
+ # Info for
16
+ include DataMapper::Types
17
+
18
+ def self.id
19
+ include DataMapper::Resource
20
+ self.property(:id, Serial)
21
+ @scaffold_properties = []
22
+ init
23
+ end
24
+
25
+ def self.init
26
+ end
27
+
28
+ # Return properties that should be shown for a given context
29
+ def self.form_props(context)
30
+ @scaffold_properties.select{|p| p[:levels].include?(context) }
31
+ end
32
+
33
+ # Helper to wrap properties into admin level
34
+ def self.admin(&block)
35
+ @levels = [:admin, :orange]
36
+ instance_eval(&block)
37
+ @levels = false
38
+ end
39
+
40
+ # Helper to wrap properties into orange level
41
+ def self.orange(&block)
42
+ @levels = [:orange]
43
+ instance_eval(&block)
44
+ @levels = false
45
+ end
46
+
47
+ # Helper to wrap properties into front level
48
+ def self.front(&block)
49
+ @levels = [:live, :admin, :orange]
50
+ instance_eval(&block)
51
+ @levels = false
52
+ end
53
+
54
+ # Define a helper for title type database stuff
55
+ # Show in a context if wrapped in one of the helpers
56
+ def self.title(name, opts = {})
57
+ @scaffold_properties << {:name => name, :type => :title, :levels => @levels}.merge(opts) if @levels
58
+ self.property(name, String, opts)
59
+ end
60
+
61
+ # Define a helper for fulltext type database stuff
62
+ # Show in a context if wrapped in one of the helpers
63
+ def self.fulltext(name, opts = {})
64
+ @scaffold_properties << {:name => name, :type => :fulltext, :levels => @levels, :opts => opts} if @levels
65
+ self.property(name, Text, opts)
66
+ end
67
+
68
+ # Define a helper for input type="text" type database stuff
69
+ # Show in a context if wrapped in one of the helpers
70
+ def self.text(name, opts = {})
71
+ @scaffold_properties << {:name => name, :type => :text, :levels => @levels, :opts => opts} if @levels
72
+ self.property(name, String, opts)
73
+ end
74
+
75
+ # Define a helper for input type="text" type database stuff
76
+ # Show in a context if wrapped in one of the helpers
77
+ def self.string(name, opts = {})
78
+ self.text(name, opts)
79
+ end
80
+
81
+ # Override DataMapper to include context sensitivity (as set by helpers)
82
+ def self.property(name, type, opts = {})
83
+ my_type = type.to_s.downcase.to_sym
84
+ @scaffold_properties << {:name => name, :type => my_type, :levels => @levels}.merge(opts) if @levels
85
+ property(name, type, opts)
86
+ end
87
+
88
+
89
+ # For more generic cases, use same syntax as DataMapper
90
+ # This will make it an admin property though.
91
+ def self.admin_property(name, type, opts = {})
92
+ my_type = type.to_s.downcase.to_sym
93
+ @scaffold_properties << {:name => name, :type => my_type, :levels => [:admin, :orange]}.merge(opts)
94
+ property(name, type, opts)
95
+ end
96
+
97
+ # For more generic cases, use same syntax as DataMapper
98
+ # This will make it a front property though.
99
+ def self.front_property(name, type, opts = {})
100
+ my_type = type.to_s.downcase.to_sym
101
+ @scaffold_properties << {:name => name, :type => my_type, :levels => [:live, :admin, :orange]}.merge(opts)
102
+ property(name, type, opts)
103
+ end
104
+
105
+ # For more generic cases, use same syntax as DataMapper
106
+ # This will make it an orange property though.
107
+ def self.orange_property(name, type, opts = {})
108
+ my_type = type.to_s.downcase.to_sym
109
+ @scaffold_properties << {:name => name, :type => my_type, :levels => [:orange]}.merge(opts)
110
+ property(name, type, opts)
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,9 @@
1
+ require 'dm-core'
2
+
3
+ module Orange
4
+ class SiteCarton < Carton
5
+ def self.init
6
+ belongs_to :orange_site, 'Orange::Site'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,197 @@
1
+ require 'dm-core'
2
+ require 'rack'
3
+ require 'rack/builder'
4
+
5
+ module Orange
6
+ # Declare submodules for later use
7
+ module Pulp; end
8
+ module Mixins; end
9
+
10
+ # Allow mixins directly from Orange
11
+ def self.mixin(inc)
12
+ Core.mixin inc
13
+ end
14
+
15
+ # Allow pulp directly from Orange
16
+ def self.add_pulp(inc)
17
+ Packet.mixin inc
18
+ end
19
+
20
+ # Core is one of two main sources of interaction for Orange Applications
21
+ #
22
+ # All portions of Orange based code have access to the Core upon
23
+ # initialization. Orange allows access to individual resources,
24
+ # and also allows single point for event registration and firing.
25
+ #
26
+ # Functionality of the core can be extended by loading resources,
27
+ # or by mixins that directly affect the Core. Generally, resources
28
+ # are the less convoluted (easier to debug) way to do it.
29
+ class Core
30
+ # Sets the default options for Orange Applications
31
+ DEFAULT_CORE_OPTIONS =
32
+ {
33
+ :contexts => [:live, :admin, :orange],
34
+ :default_context => :live,
35
+ :default_resource => :not_found,
36
+ :default_database => 'sqlite3::memory:'
37
+ } unless defined?(DEFAULT_CORE_OPTIONS)
38
+
39
+ # Args will be set to the @options array.
40
+ # Block DSL style option setting also available:
41
+ #
42
+ # orange = Orange::Core.new(:optional_option => 'foo') do
43
+ # haml true
44
+ # site_name "Banana"
45
+ # custom_router MyRouterClass.new
46
+ # end
47
+ #
48
+ # orange.options[:site_name] #=> "Banana"
49
+ #
50
+ # This method calls afterLoad when it is done. Subclasses can override
51
+ # the afterLoad method for initialization needs.
52
+ def initialize(*args, &block)
53
+ @options = Options.new(*args, &block).hash.with_defaults(DEFAULT_CORE_OPTIONS)
54
+ @resources = {}
55
+ @events = {}
56
+ @file = __FILE__
57
+ load(Orange::Parser.new, :parser)
58
+ load(Orange::Mapper.new, :mapper)
59
+ load(Orange::PageParts.new, :page_parts)
60
+ afterLoad
61
+ self
62
+ end
63
+
64
+ # Returns the orange library directory
65
+ # @return [String] the directory name indicating where the core file is
66
+ # located
67
+ def core_dir
68
+ options[:core_dir] ||= File.dirname(__FILE__)
69
+ end
70
+
71
+ # Returns the directory of the currently executing file (using Dir.pwd),
72
+ # can be overriden using the option :app_dir in initialization
73
+ #
74
+ # @return [String] the directory name of the currently running application
75
+ def app_dir
76
+ options[:app_dir] ||= Dir.pwd
77
+ end
78
+
79
+ # Called by initialize after finished loading
80
+ def afterLoad
81
+ true
82
+ end
83
+
84
+ # Returns status of a given resource by short name
85
+ # @param [Symbol] resource_name The short name of the resource
86
+ # @return [Boolean] result of has_key? in the resources list
87
+ def loaded?(resource_name)
88
+ @resources.has_key?(resource_name)
89
+ end
90
+
91
+ # Takes an instance of a Orange::Resource subclass, sets orange
92
+ # then adds it to the orange resources
93
+ #
94
+ # It can be assigned a short name to be used for accessing later
95
+ # on. If no short name is assigned, one will be generated by downcasing
96
+ # the class name and changing it to a symbol
97
+ #
98
+ # Resources must respond to set_orange, which is automatically used to
99
+ # create a link back to the Core, and to notify the resource of its assigned
100
+ # short name.
101
+ #
102
+ # @param [Orange::Resource] resource An instance of Orange::Resource subclass
103
+ # @param [optional, Symbol, String] name A short name to assign as key in Hash
104
+ # list of resources.
105
+ # Doesn't necessarily need to be a symbol, but generally is.
106
+ # Set to the class name lowercase as a symbol by default.
107
+ def load(resource, name = false)
108
+ name = resource.class.to_s.gsub(/::/, '_').downcase.to_sym if(!name)
109
+ @resources[name] = resource.set_orange(self, name)
110
+ end
111
+
112
+ # Convenience self for consistent naming across middleware
113
+ # @return [Orange::Core] self
114
+ def orange; self; end
115
+
116
+ # Registers interest in a callback for a named event.
117
+ #
118
+ # Event registration is stored as a hash list of events and arrays
119
+ # of procs to be executed on each event.
120
+ #
121
+ # @param [Symbol] event the name of the event registered for
122
+ # @param [optional, Integer] position the position to place the event in,
123
+ # by default goes to the front of the list. Doesn't necessarily need
124
+ # to be exact count, empty spaces in array are taken out. Forcing the
125
+ # event to be at 99 or some such position will typically make sure it
126
+ # happens last in the firing process.
127
+ # @param [Block] block The code to be executed upon event firing.
128
+ # Saved to an array of procs that are called when #fire is called.
129
+ # Block must accept one param, which is the intended to be the packet
130
+ # causing the block to fire, unless the event happens in setup.
131
+ def register(event, position = 0, &block)
132
+ if block_given?
133
+ if @events[event]
134
+ @events[event].insert(position, Proc.new)
135
+ else
136
+ @events[event] = Array.new.insert(position, Proc.new)
137
+ end
138
+ end
139
+ end
140
+
141
+ # Fires a callback for a given packet (or other object)
142
+ #
143
+ # @param [Symbol] event name of event something has registered for
144
+ # @param [Orange::Packet, object] packet Object, generally Orange::Packet,
145
+ # causing the fire. This is passed to each Proc registered.
146
+ # @return [Boolean] returns false if nothing has been registered for the
147
+ # event, otherwise true.
148
+ def fire(event, packet)
149
+ return false unless @events[event]
150
+ @events[event].compact!
151
+ for callback in @events[event]
152
+ callback.call(packet)
153
+ end
154
+ true
155
+ end
156
+
157
+ # Returns options of the orange core
158
+ #
159
+ # @return [Hash] Hash of options
160
+ def options
161
+ @options
162
+ end
163
+
164
+
165
+ # Accesses resources array, stored as a hash {:short_name => Resource instance,...}
166
+ #
167
+ # @param [Symbol] name the short name for the requested resource
168
+ # @return [Orange::Resource] the resource for the given short name
169
+ def [](name)
170
+ @resources[name]
171
+ end
172
+
173
+ # Includes module in the Packet class
174
+ # @param [Module] inc module to be included
175
+ def add_pulp(inc)
176
+ self.class.add_pulp inc
177
+ end
178
+
179
+ # Includes module in this class
180
+ # @param [Module] inc module to be included
181
+ def mixin(inc)
182
+ self.class.mixin inc
183
+ end
184
+
185
+ # Includes module in this class
186
+ # @param [Module] inc module to be included
187
+ def self.mixin(inc)
188
+ include inc
189
+ end
190
+
191
+ # Includes module in the Packet class
192
+ # @param [Module] inc module to be included
193
+ def self.add_pulp(inc)
194
+ Packet.mixin inc
195
+ end
196
+ end
197
+ end