simplec 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +308 -0
  4. data/Rakefile +36 -0
  5. data/app/assets/config/simplec_manifest.js +2 -0
  6. data/app/assets/fonts/simplec/summernote.eot +0 -0
  7. data/app/assets/fonts/simplec/summernote.ttf +0 -0
  8. data/app/assets/fonts/simplec/summernote.woff +0 -0
  9. data/app/assets/javascripts/simplec/application.js +13 -0
  10. data/app/assets/javascripts/simplec/summernote-config.js +82 -0
  11. data/app/assets/javascripts/simplec/summernote.js +7046 -0
  12. data/app/assets/stylesheets/simplec/application.css +15 -0
  13. data/app/assets/stylesheets/simplec/summernote.scss +691 -0
  14. data/app/constraints/simplec/subdomains.rb +23 -0
  15. data/app/controllers/simplec/application_controller.rb +9 -0
  16. data/app/controllers/simplec/pages_controller.rb +19 -0
  17. data/app/helpers/simplec/application_helper.rb +4 -0
  18. data/app/jobs/simplec/application_job.rb +4 -0
  19. data/app/mailers/simplec/application_mailer.rb +6 -0
  20. data/app/models/simplec/application_record.rb +5 -0
  21. data/app/models/simplec/embedded_image.rb +15 -0
  22. data/app/models/simplec/page.rb +203 -0
  23. data/app/models/simplec/subdomain.rb +30 -0
  24. data/app/views/simplec/fields/_editor.html.erb +5 -0
  25. data/app/views/simplec/fields/_file.html.erb +16 -0
  26. data/app/views/simplec/fields/_image.html.erb +16 -0
  27. data/app/views/simplec/fields/_string.html.erb +4 -0
  28. data/app/views/simplec/fields/_text.html.erb +4 -0
  29. data/app/views/simplec/pages/show.html.erb +2 -0
  30. data/config/routes.rb +8 -0
  31. data/db/migrate/20170809204353_create_simplec_pages.rb +30 -0
  32. data/db/migrate/20170809204511_create_simplec_subdomains.rb +12 -0
  33. data/db/migrate/20170809210304_create_simplec_embedded_images.rb +15 -0
  34. data/lib/simplec/action_controller/extensions.rb +56 -0
  35. data/lib/simplec/action_view/helper.rb +118 -0
  36. data/lib/simplec/embedded_image_actions.rb +37 -0
  37. data/lib/simplec/engine.rb +18 -0
  38. data/lib/simplec/version.rb +3 -0
  39. data/lib/simplec.rb +5 -0
  40. data/lib/tasks/simplec_tasks.rake +4 -0
  41. metadata +41 -2
@@ -0,0 +1,9 @@
1
+ module Simplec
2
+ class ApplicationController < ::ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ after_action do
6
+ Thread.current[:simplec_subdomain] = nil
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ require_dependency "simplec/application_controller"
2
+
3
+ module Simplec
4
+ class PagesController < ApplicationController
5
+
6
+ def show
7
+ @page = page("/#{params[:path]}")
8
+ render layout: layout(@page)
9
+ end
10
+
11
+ private
12
+
13
+ def layout(page)
14
+ [page.layout, page.subdomain.default_layout, 'public']. # TODO allow config for public
15
+ reject(&:blank?).first
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ module Simplec
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Simplec
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Simplec
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Simplec
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ module Simplec
2
+ class EmbeddedImage < ApplicationRecord
3
+ belongs_to :embeddable,
4
+ polymorphic: true
5
+
6
+ dragonfly_accessor :asset
7
+
8
+ def url
9
+ return unless self.asset
10
+ return self.asset.url unless persisted?
11
+ self.asset.url(ei: Base64.urlsafe_encode64(self.id.to_s))
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,203 @@
1
+ # Simplec::Page
2
+ #
3
+ # This class represents a page in the system.
4
+ #
5
+ # Each page has a class located in:
6
+ # app/models/page/NAME.html.erb
7
+ #
8
+ # Each page has a partial template in:
9
+ # app/views/pages/_NAME.html.erb
10
+ #
11
+ # Where NAME is the demodulized, snake-case name of the Page Subclasss. For
12
+ # example:
13
+ #
14
+ # class Page::Home < Page
15
+ # field :h1
16
+ # end
17
+ #
18
+ # Would live in app/models/page/home.html.erb and have a template in
19
+ # app/views/pages/_home.html.erb.
20
+ #
21
+ # Each page has the following attributes by default:
22
+ # - page.title
23
+ #
24
+ # Read the documentation below for the class method .field, it is the most
25
+ # used method.
26
+ #
27
+ #
28
+ module Simplec
29
+ class Page < ApplicationRecord
30
+
31
+
32
+ # TODO Document FILE_FIELDS constant
33
+ #
34
+ FILE_FIELDS = [:file, :image].freeze
35
+
36
+ belongs_to :subdomain,
37
+ optional: false
38
+ belongs_to :parent,
39
+ class_name: 'Page',
40
+ optional: true
41
+ has_many :childern,
42
+ class_name: 'Page',
43
+ foreign_key: :parent_id
44
+ has_many :embedded_images,
45
+ as: :embeddable,
46
+ dependent: :delete_all
47
+
48
+ validates :type,
49
+ presence: true
50
+ validates :path,
51
+ presence: true,
52
+ uniqueness: { scope: :subdomain_id }
53
+ validates :layout,
54
+ inclusion: {in: :layouts, allow_blank: true}
55
+ validates :title,
56
+ presence: true
57
+
58
+ before_validation :match_parent_subdomain
59
+ before_validation :build_path
60
+ after_save :link_embedded_images!
61
+
62
+ # Define a field on the page.
63
+ #
64
+ # - name - name of field
65
+ # - options[:type] - :string (default), :text, :editor, :file, :image
66
+ # :string - yields a text input
67
+ # :text - yields a textarea
68
+ # :editor - yields a summernote editor
69
+ # :file - yields a file field
70
+ # :image - yields a file field with image preview
71
+ #
72
+ # There is as template for each type for customization located in:
73
+ # app/views/shared/fields/_TYPE.html.erb
74
+ #
75
+ # Defines a field on a subclass. This creates a getter and setter for the
76
+ # name passed in. The options are used when building the administration forms.
77
+ #
78
+ # Regular dragonfly validations are available on :file and :image fields.
79
+ # http://markevans.github.io/dragonfly/models#validations
80
+ #
81
+ def self.field(name, options={})
82
+ fields[name] = {name: name, type: :string}.merge(options)
83
+ if FILE_FIELDS.member?(fields[name][:type])
84
+ dragonfly_accessor name
85
+ data_field :"#{name}_uid"
86
+ data_field :"#{name}_name"
87
+ else
88
+ data_field(name)
89
+ end
90
+ end
91
+
92
+ # List fields
93
+ #
94
+ def self.fields
95
+ @fields ||= Hash.new
96
+ end
97
+
98
+ # Return a constantized type, whitelisted by known subclasses.
99
+ #
100
+ def self.type(type)
101
+ ::Page rescue raise '::Page not defined, define it in app/models'
102
+ raise 'Unsupported Page Type; define in app/models/page/' unless ::Page.subclasses.map(&:name).
103
+ member?(type)
104
+ type.constantize
105
+ end
106
+
107
+ # Return names of fields.
108
+ #
109
+ # type: :file, is the only option
110
+ #
111
+ def self.field_names(type=nil)
112
+ _fields = case type
113
+ when :file
114
+ fields.select {|k, v| FILE_FIELDS.member?(v[:type])}
115
+ else
116
+ fields
117
+ end
118
+ _fields.keys
119
+ end
120
+
121
+ # Return field options for building forms.
122
+ #
123
+ def field_options
124
+ self.class.fields.values
125
+ end
126
+
127
+ # List parents, closest to furthest.
128
+ #
129
+ def parents
130
+ page, parents = self, Array.new
131
+ while page.parent
132
+ page = page.parent
133
+ parents << page
134
+ end
135
+ parents
136
+ end
137
+
138
+ # Before validation hook.
139
+ #
140
+ # Build the path of the page to be used in routing.
141
+ #
142
+ def build_path
143
+ _pages = self.parents.reverse + [self]
144
+ self.path = "/#{_pages.map(&:slug).reject(&:blank?).join('/')}"
145
+ end
146
+
147
+ # Before validation hook
148
+ #
149
+ # All pages need to have a matching subdomain to parent page
150
+ #
151
+ def match_parent_subdomain
152
+ return unless self.parent
153
+ self.subdomain = self.parent.subdomain
154
+ end
155
+
156
+ def find_embedded_images
157
+ text = self.fields.values.join(' ')
158
+ matches = text.scan(/ei=([^&]*)/)
159
+ encoded_ids = matches.map(&:first)
160
+ ids = encoded_ids.map { |eid| Base64.urlsafe_decode64(URI.unescape(eid)) }
161
+ EmbeddedImage.includes(:embeddable).find(ids)
162
+ end
163
+
164
+ def link_embedded_images!
165
+ images = self.find_embedded_images
166
+ images.each do |image|
167
+ raise AlreadyLinkedEmbeddedImage if image.embeddable &&
168
+ image.embeddable != self
169
+ image.update!(embeddable: self)
170
+ end
171
+ end
172
+
173
+ def layouts
174
+ @layouts ||= Subdomain.new.layouts
175
+ end
176
+
177
+ module Normalizers
178
+ def slug=(val)
179
+ val = val ? val.to_s.split('/').reject(&:blank?).join('/') : val
180
+ super val
181
+ end
182
+
183
+ def slug
184
+ if self.parent_id && self.parent.nil?
185
+ self.path.to_s.split('/').reject(&:blank?).join('/')
186
+ else
187
+ super
188
+ end
189
+ end
190
+ end
191
+ prepend Normalizers
192
+
193
+ class AlreadyLinkedEmbeddedImage < StandardError; end
194
+
195
+ private
196
+
197
+ def self.data_field(name)
198
+ define_method(name) { fields[name.to_s] }
199
+ define_method("#{name}=") { |val| fields[name.to_s] = val }
200
+ end
201
+ end
202
+ end
203
+
@@ -0,0 +1,30 @@
1
+ module Simplec
2
+ class Subdomain < ApplicationRecord
3
+ has_many :pages
4
+ has_and_belongs_to_many :document_sets
5
+ has_and_belongs_to_many :documents
6
+
7
+ validates :name,
8
+ presence: true,
9
+ exclusion: { in: %w(admin) }
10
+ validates :default_layout,
11
+ inclusion: {in: :layouts, allow_blank: true}
12
+
13
+ def layouts
14
+ @layouts ||= Dir[Rails.root.join('app/views/layouts').to_s + "/*.html.*"].
15
+ map{|n| File.basename(n).split('.', 2).first }.
16
+ reject{|n| n =~ /\A_/ || n =~ /mailer/ || n =~ /application/ || n =~ /sessions/}.
17
+ sort
18
+ end
19
+
20
+ module Normalizers
21
+
22
+ # Force lowercase name
23
+ #
24
+ def name=(val)
25
+ super (val ? val.to_s.strip.downcase : val)
26
+ end
27
+ end
28
+ prepend Normalizers
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ <div class="form-group">
2
+ <%= f.label name, class: 'control-label' %>
3
+ <%= f.hidden_field name, id: "#{name}-#{f.object.object_id}"%>
4
+ <%= tag :div, class: 'editor-field', data: {input: "##{name}-#{f.object.object_id}"} %></div>
5
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="form-group">
2
+ <%= f.label name, class: 'control-label' %>
3
+ <% if f.object.send(name) %>
4
+ <div>
5
+ <p class="form-control-static"><%= f.object.send(name).name %></p>
6
+ <div class="checkbox">
7
+ <label>
8
+ <%= f.check_box :"remove_#{name}" %>
9
+ Remove
10
+ </label>
11
+ </div>
12
+ </div>
13
+ <% end %>
14
+ <%= f.file_field name %>
15
+ <%= f.hidden_field :"retained_#{name}" %>
16
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="form-group">
2
+ <%= f.label name, class: 'control-label' %>
3
+ <% if f.object.send(name) %>
4
+ <div>
5
+ <%= image_tag f.object.send(name).thumb('100x').url %>
6
+ <div class="checkbox">
7
+ <label>
8
+ <%= f.check_box :"remove_#{name}" %>
9
+ Remove
10
+ </label>
11
+ </div>
12
+ </div>
13
+ <% end %>
14
+ <%= f.file_field name %>
15
+ <%= f.hidden_field :"retained_#{name}" %>
16
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="form-group">
2
+ <%= f.label name, class: 'control-label' %>
3
+ <%= f.text_field name, class: 'form-control' %>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="form-group">
2
+ <%= f.label name, class: 'control-label' %>
3
+ <%= f.text_area name, class: 'form-control' %>
4
+ </div>
@@ -0,0 +1,2 @@
1
+ <%= head @page %>
2
+ <%= template @page %>
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ Simplec::Engine.routes.draw do
2
+
3
+ scope constraints: Simplec::Subdomains do
4
+ root 'pages#show'
5
+ get '*path', to: 'pages#show'
6
+ end
7
+
8
+ end
@@ -0,0 +1,30 @@
1
+ class CreateSimplecPages < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :simplec_pages, id: :uuid, default: 'gen_random_uuid()' do |t|
4
+ t.string :type
5
+ t.uuid :subdomain_id
6
+ t.uuid :parent_id
7
+ t.string :path
8
+ t.string :slug
9
+ t.string :title
10
+ t.string :meta_description
11
+ t.jsonb :fields
12
+ t.string :layout
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :simplec_pages, :type
18
+ add_index :simplec_pages, :subdomain_id
19
+ add_index :simplec_pages, :parent_id
20
+ add_index :simplec_pages, :path
21
+ add_index :simplec_pages, [:subdomain_id, :path], unique: true
22
+
23
+ reversible do |dir|
24
+ dir.up {
25
+ execute "ALTER TABLE simplec_pages ALTER COLUMN fields SET DEFAULT '{}'::JSONB"
26
+ }
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ class CreateSimplecSubdomains < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :simplec_subdomains, id: :uuid, default: 'gen_random_uuid()' do |t|
4
+ t.string :name
5
+ t.string :default_layout
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :simplec_subdomains, :name, unique: true
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ class CreateSimplecEmbeddedImages < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :simplec_embedded_images, id: :uuid, default: 'gen_random_uuid()' do |t|
4
+ t.string :embeddable_type
5
+ t.integer :embeddable_id
6
+ t.string :asset_uid
7
+ t.string :asset_name
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :simplec_embedded_images, [:embeddable_type, :embeddable_id],
13
+ name: 'simplec_embedded_images_type_id_index'
14
+ end
15
+ end
@@ -0,0 +1,56 @@
1
+ module Simplec
2
+ module ActionController
3
+ module Extensions
4
+
5
+ def self.included(receiver)
6
+ receiver.helper_method :subdomain, :page,
7
+ :simplec_path, :simplec_url
8
+ end
9
+
10
+ def subdomain(name=nil)
11
+ name ||= request.subdomain
12
+ @_subdomains ||= Hash.new
13
+ return @_subdomains[name] if @_subdomains[name]
14
+ @_subdomains[name] = Subdomain.find_by!(name: name)
15
+ end
16
+
17
+ def page(path, options={})
18
+ @_page ||= Hash.new
19
+ return @_page[path] if @_page[path]
20
+
21
+ _subdomain = subdomain
22
+ _subdomain = Subdomain.find_by!(
23
+ name: options[:subdomain]
24
+ ) if options[:subdomain]
25
+
26
+ @_page[path] = _subdomain.pages.find_by!(path: path)
27
+ end
28
+
29
+ def simplec_path(page_or_path, options={})
30
+ # TODO cache page_paths
31
+ _page = page_or_path.is_a?(Page) ?
32
+ page_or_path : page(page_or_path, options)
33
+
34
+ unless _page
35
+ raise ActiveRecord::RecordNotFound if options[:raise]
36
+ return nil
37
+ end
38
+
39
+ _page.path
40
+ end
41
+
42
+ def simplec_url(page_or_path, options={})
43
+ # TODO cache page_urls
44
+ _page = page_or_path.is_a?(Page) ?
45
+ page_or_path : page(page_or_path, options)
46
+
47
+ unless _page
48
+ raise ActiveRecord::RecordNotFound if options[:raise]
49
+ return nil
50
+ end
51
+
52
+ URI.join(root_url(subdomain: _page.subdomain.try(:name)), _page.path).to_s
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,118 @@
1
+ module Simplec
2
+ module ActionView
3
+ module Helper
4
+
5
+ def head(page)
6
+ content_for :title, page.title
7
+ content_for :meta_description, page.meta_description
8
+ end
9
+
10
+ def title
11
+ content_for :title
12
+ end
13
+
14
+ def meta_description_tag
15
+ tag :meta, name: 'description', content: content_for(:meta_description)
16
+ end
17
+
18
+ def template(page)
19
+ _template = page.type.demodulize.downcase
20
+ render "pages/#{_template}"
21
+ end
22
+
23
+ def page_field(f, options={})
24
+ render "simplec/fields/#{options[:type]}", options.merge(f: f)
25
+ end
26
+
27
+ # Retreive a document.
28
+ #
29
+ # slug - matching a Document slug.
30
+ # options[:subdomain] - a string matching a Subdomain name, find a document
31
+ # from another subdomain
32
+ # options[:raise] - if true, casuse a 404 if document isn't found
33
+ #
34
+ # Example:
35
+ #
36
+ # -# Save as...
37
+ # <%= link_to doc('/permission/general').name,
38
+ # doc('/permission/general').path,
39
+ # download: true %>
40
+ #
41
+ # -# Display in new window...
42
+ # <%= link_to doc('/permission/general').name,
43
+ # doc('/permission/general').path,
44
+ # target: '_blank' %>
45
+ #
46
+ def doc(slug, options={})
47
+ @_docs ||= Hash.new
48
+ key = "[#{options[:subdomain]}][#{slug}]"
49
+ return @_docs[key] if @_docs[key]
50
+ @_docs[key] = subdomain(options[:subdomain]).
51
+ documents.find_by(slug: slug)
52
+ raise ActiveRecord::RecordNotFound if options[:raise] && !@_docs[key]
53
+ @_docs[key]
54
+ end
55
+
56
+ # Retreive a document.
57
+ #
58
+ # slug - matching a Document slug.
59
+ # options[:subdomain] - a string matching a Subdomain name, find a document
60
+ # set from another subdomain
61
+ # options[:raise] - if true, casuse a 404 if document set isn't found
62
+ #
63
+ # Example:
64
+ #
65
+ # <% docset('/faith').each do |doc| %>
66
+ # <%= link_to doc.name, doc.path %>
67
+ # <% end %>
68
+ #
69
+ def docset(slug, options={})
70
+ @_docsets ||= Hash.new
71
+ key = "[#{options[:subdomain]}][#{slug}]"
72
+ return @_docsets[key] if @_docsets[key]
73
+ set = subdomain(options[:subdomain]).
74
+ document_sets.find_by(slug: slug)
75
+ raise ActiveRecord::RecordNotFound if options[:raise] && !set
76
+ @_docsets[key] = set.documents
77
+ end
78
+
79
+ # page_or_path - a page object or a path of a page
80
+ # options[:subdomain] - a string matching a Subdomain name, find a page
81
+ # from another subdomain
82
+ # options[:raise] - if true, casuse a 404 if a page set isn't found
83
+ #
84
+ # Example:
85
+ #
86
+ # <% subpages('/give-volunteer', conditions: ->{ order(title: :asc) }).each do |page| %>
87
+ # ... do something with page ...
88
+ # <% end%>
89
+ def subpages(page_or_path, options={})
90
+ if options[:conditions] && !options[:conditions].respond_to?(:call)
91
+ raise ArgumentError, <<-ERROR
92
+ #{options[:conditions]} was passed as :conditions but is not callable.
93
+ "Pass a callable instead: `conditions: -> { where(approved: true) }`
94
+ ERROR
95
+ end
96
+
97
+ @_subpages ||= Hash.new # TODO apply new conditions after cache
98
+ key = "[#{options[:subdomain]}][#{page_or_path}]"
99
+
100
+ unless @_subpages[key]
101
+ page = page_or_path.is_a?(Page) ? page_or_path : nil
102
+ page ||= subdomain(options[:subdomain]).pages.find_by(path: page_or_path)
103
+
104
+ unless page
105
+ raise ActiveRecord::RecordNotFound if options[:raise]
106
+ return @_subpages[key] = Array.new
107
+ end
108
+
109
+ @_subpages[key] = page.subpages
110
+ end
111
+
112
+ @_subpages[key].respond_to?(:merge) && options[:conditions] ?
113
+ @_subpages[key].merge(options[:conditions]) : @_subpages[key]
114
+ end
115
+ end
116
+
117
+ end
118
+ end
@@ -0,0 +1,37 @@
1
+ module Simplec
2
+ module EmbeddedImageActions
3
+ module ClassMethods; end
4
+
5
+ module InstanceMethods
6
+
7
+ def create
8
+ @embedded_image = EmbeddedImage.new(embedded_image_params)
9
+ if @embedded_image.save
10
+ respond_to do |format|
11
+ format.json {
12
+ render :show, status: 201, location: @embedded_image.url
13
+ }
14
+ end
15
+ else
16
+ respond_to do |format|
17
+ format.json {
18
+ render status: 422, json: @embedded_image.errors
19
+ }
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def embedded_image_params
27
+ params.permit(:asset_url, :asset_name)
28
+ end
29
+
30
+ end
31
+
32
+ def self.included(receiver)
33
+ receiver.extend ClassMethods
34
+ receiver.send :include, InstanceMethods
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require 'simplec/action_controller/extensions'
2
+ require 'simplec/action_view/helper'
3
+
4
+ module Simplec
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace Simplec
7
+
8
+ initializer "simplec_controller_extensions" do
9
+ ActiveSupport.on_load(:action_controller_base) {
10
+ prepend Simplec::ActionController::Extensions
11
+ helper Simplec::ActionView::Helper
12
+ }
13
+ ActiveSupport.on_load(:active_record) {
14
+ Dir["#{Rails.root}/app/models/page/*.rb"].each {|file| require_dependency file }
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Simplec
2
+ VERSION = '0.1.2'
3
+ end
data/lib/simplec.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "simplec/engine"
2
+
3
+ module Simplec
4
+ # TODO configuration options
5
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :simplec do
3
+ # # Task goes here
4
+ # end