atd 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e4b052a0c402077fa55a5c00e96db96c1963430a
4
- data.tar.gz: 487ab2f6ce28280effbf0a1faff0d9b9959cd389
3
+ metadata.gz: ba460feaa80c9fb85ebab903d9e62e0300e579cd
4
+ data.tar.gz: 2c917612c41a5642f8115a1fd76e01e4a4471c11
5
5
  SHA512:
6
- metadata.gz: db837886aa4f434403f51f8f1d6cf796c6cfd0cc1e4ef095c336b967a0c5d22dc15f6d93e20afe7e3f8f897da6f7659d0e13be366a5fb4b73730c886f4996640
7
- data.tar.gz: 4075dd51b6e64f6977c13b6d6d684ab66c5abca0c992b5efa0752c07b251835bd78e13984716c1fb7c3b1dbfee7cd90a04b851813da7b291a4b1fb53e222e7be
6
+ metadata.gz: 762ce1b5272cac9f2f643fc98a69f97b084f19aac2c971a247a09e8dd704d7359ea383a740aa89f7b4d0ee7ad17183cb8c77e4ebffa2cb605cf2063d2e464be1
7
+ data.tar.gz: d223ee6e845e202f03f04f04a36518ddc184a79fe8fa7b8d92ab476b62ef95c6b81123664e8d80401cee5b87c4675e38b9f6c9429044e70a988359b1b9fbe087
data/.gitignore CHANGED
@@ -1,14 +1,14 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  /Gemfile.lock
4
- /_yardoc/
5
4
  /doc/
6
5
  /pkg/
7
6
  /spec/reports/
8
- /tmp/
7
+ tmp
9
8
  .DS_Store
10
9
  .idea
11
10
  coverage
12
11
  /*.gem
13
12
  /updated_README.md
14
- .dev-rubocop.yml
13
+ .dev-rubocop.yml
14
+ .ruby-version
@@ -1,12 +1,63 @@
1
- image: ruby:2.3.1
1
+ image: ruby:2.4.0
2
2
 
3
3
  before_script:
4
4
  - ruby -v
5
5
  - which ruby
6
+ - gem environment
7
+ - gem install rubygems-update
8
+ - gem install rubygems-update --version 2.6.3
9
+ - gem update --system 2.6.10
6
10
  - gem install bundler --no-ri --no-rdoc
11
+ - gem environment
7
12
  - bundle install --jobs $(nproc) "${FLAGS[@]}"
8
13
 
9
- test:
14
+ test-2.4.0:
15
+ image: ruby:2.4.0
16
+ script:
17
+ - bundle exec rake test
18
+
19
+ test-2.3.3:
20
+ image: ruby:2.3.3
21
+ script:
22
+ - bundle exec rake test
23
+
24
+ test-2.3.2:
25
+ image: ruby:2.3.2
26
+ script:
27
+ - bundle exec rake test
28
+
29
+ test-2.3.1:
30
+ image: ruby:2.3.1
31
+ script:
32
+ - bundle exec rake test
33
+
34
+ test-2.3.0:
35
+ image: ruby:2.3.0
36
+ script:
37
+ - bundle exec rake test
38
+
39
+ test-2.2.6:
40
+ image: ruby:2.2.6
41
+ script:
42
+ - bundle exec rake test
43
+
44
+ test-2.2.5:
45
+ image: ruby:2.2.5
46
+ script:
47
+ - bundle exec rake test
48
+
49
+ test-2.2.4:
50
+ image: ruby:2.2.4
51
+ script:
52
+ - bundle exec rake test
53
+
54
+ test-2.2.3:
55
+ image: ruby:2.2.3
56
+ script:
57
+ - bundle exec rake test
58
+
59
+ test-2.2.2:
60
+ image: ruby:2.2.2
10
61
  script:
11
62
  - bundle exec rake test
12
63
 
@@ -14,14 +14,11 @@ Style/ParallelAssignment:
14
14
  Style/Documentation:
15
15
  Enabled: false
16
16
 
17
- Style/ModuleFunction:
18
- Enabled: false
19
-
20
17
  Style/ClassAndModuleChildren:
21
18
  Enabled: false
22
19
 
23
20
  Metrics:
24
21
  Enabled: false
25
22
 
26
- Lint/UnifiedInteger:
23
+ Style/FrozenStringLiteralComment:
27
24
  Enabled: false
data/CHANGELOG CHANGED
@@ -1,4 +1,10 @@
1
1
  Current Repo Version:
2
+ - Cleaned up code for some built-in class modifications, replacing them with more limited in scope refinements. (#30)
3
+ - Allowed greater flexibility in compilation and precomilation by passing those methods the settings hash passed during route creation. (#27)
4
+ - Upgraded to comply with 2.4.0 by testing for Integer instead of Fixnum. To preserve backwards compatibility, we made different behaviors for older ruby versions. (#24)
5
+ - Completely rewrote README to be much easier to read and more informative. (#23)
6
+ - Added sinatra-esque helper methods instead of a ugly @http instance variable. (#18)
7
+ - Fixed some issues with request in main (DefaultApp). (#31, #32)
2
8
 
3
9
  v0.4.0
4
10
  - Fixed an issue where routes without blocks wouldn't return status codes (#9)
@@ -0,0 +1,28 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :minitest do
19
+ # with Minitest::Unit
20
+ watch(%r{^test/(.*)\/?test_(.*)\.rb$})
21
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
22
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
23
+
24
+ # with Minitest::Spec
25
+ # watch(%r{^spec/(.*)_spec\.rb$})
26
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
27
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
28
+ end
data/README.md CHANGED
@@ -13,160 +13,123 @@
13
13
  [gem version link]: https://rubygems.org/gems/atd
14
14
 
15
15
 
16
- [ATD](#atd)
17
- - [Installation](#installation)
18
- - [Usage](#usage)
19
- - [Setup](#setup)
20
- - [Routing](#routing)
21
- - [Basic Routing](#basic-routing)
22
- - [Options Hash](#options-hash)
23
- - [Serving Files](#serving-files)
24
- - [Advanced Routing](#advanced-routing)
25
- - [DefaultApp](#defaultapp)
26
- - [Inheritance](#inheritance)
27
- - [Blocks](#blocks)
28
- - [@http](#http)
29
- - [Controllers](#controllers)
30
- - [Apps](#apps)
31
- - [App Creation](#app-creation)
32
- - [Starting the App](#starting-the-app)
33
- - [App Routing](#app-routing)
34
- - [Logging](#logging)
35
- - [Compilation](#compilation)
36
- - [Design Paradigms](#design-paradigms)
37
- - [Documentation](#documentation)
38
- - [Development](#development)
39
- - [Contributing](#contributing)
40
-
41
-
42
16
  # ATD
43
17
 
44
- ATD is a new web development backend framework for Ruby. It is built on top of rack, and is meant to be small easy and light. That is why it provides a very minimalist way of writing your backend. It treats a request as a request, without splitting it based on the HTTP method.
45
-
46
- ## Installation
18
+ Hello! I assume if you're reading this you really want to know about this really cool, interesting new framework that I made. Well, in that case you've come to the right place. ATD is a small modular framework meant to combine the benefits from [rails](http://rubyonrails.org/) and [sinatra](http://www.sinatrarb.com/). I originally used sinatra, and ran into issues with scaling, and so hence I did the only logical thing and wrote a new framework. I just found that because of sinatra's "build it from the ground up" philosophy, I couldn't make anything too large without making a mass of spaghetti code. That is why this framework allows you to use rails concepts like MVC and moduarly use different components in development and production. It supports the simple sinatra DSL syntax (for the most part) to make small apps, and allows big controllers and models for separation of concerns in larger apps, which is more of a rails philosophy.
47
19
 
48
- Add this line to your application's Gemfile:
20
+ Anyways, I've worked really hard on this, and I would love for you to try it out and maybe even contribute! Feel free to reach out to me at [atd@developingtechnician.com](mailto:atd@developingtechnician.com) with questions or concerns, or if it's something concrete you want me to change, just open an issue.
49
21
 
50
- ```ruby
51
- gem 'atd', :git => 'https://gitlab.com/izwick-schachter/atd.git'
52
- ```
22
+ ## Installation
53
23
 
54
- Or, (not recommended) you can use only the rubygems version, which you can use with:
24
+ Pretty simple, the usual `gem install atd` if you want to use without bundler, or just add it to your Gemfile with bundler. The recommended setup is to use the master branch of the git repo by adding to your Gemfile:
55
25
 
56
26
  ```ruby
57
- gem 'atd'
27
+ gem 'atd', git: 'https://gitlab.com/izwick-schachter/atd.git', branch: "master"
58
28
  ```
59
29
 
60
- And then execute:
61
-
62
- $ bundle
63
-
64
- Or install it from the repository with:
30
+ Or if you want to live on the edge, where all the latest and greatest features are, change `branch` to `"development"`. Keep in mind, this may sometimes not work, so use at your own risk. When you do this, it's recommended that you go into the git repo and choose a specific commit and lock that in by setting `:ref` to the commit hash.
65
31
 
66
- $ git clone https://gitlab.com/izwick-schachter/atd.git
67
- $ cd atd
68
- $ bundle exec rake install
32
+ ## The Basics
69
33
 
70
- This will also allow you to use the current development version (USE WITH CAUTION), and to use that you can `git checkout development` and then `bundle exec rake install`.
34
+ `ATD` is structured around a two basic constructs: `App`s and `Route`s.
71
35
 
72
- ## Setup
73
-
74
- Setup is as easy as `require "atd"` at the top of whatever file you need it in
36
+ ### Apps
75
37
 
76
- ## Routing
38
+ An `App` is a class which is a functional rack app. It has an instance method `call` as the rack spec requires, and it contains all the things you need to use `ATD`. An `App` is just a class that extends `ATD::App`. That said, for most usages you will never need to know anything about `App`s. Unless you need multiple different `App`s in the same file, you don't need to touch it. That is because `ATD` kindly treats `main` as an `App` called `DefaultApp`. All that means is that anything created in `main` is added to `DefaultApp`. So, whenever you do something that is not in an `App` you have created, you can just know in the back of your head that you are impacting `DefaultApp`.
77
39
 
78
- ### Basic Routing
40
+ ### Routes
79
41
 
80
- On the lowest level it can be used serve files or any strings when a path is requested (and optionally when a specific HTTP method is used). Here is a basic route that will serve `Hello World` when `/` is sent any HTTP request (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`):
42
+ The second basic construct is a `Route`. These are things which you should understand, because these are how `ATD` processes most of the things you do. Every route belongs to an `App`, typically `DefaultApp` as mentioned [above](#apps). In it's most simple form, a `Route` simply says "When `/whatever` is requested, return this file and/or run this code". The syntax will certainly remind you of sinatra:
81
43
 
82
44
  ```ruby
83
- request "/", "Hello World"
45
+ request "/some/path", "my_file.html.erb"
46
+ # Also aliased to:
47
+ req "/some/path", "my_file.html.erb"
48
+ # And
49
+ r "/some/path", "my_file.html.erb"
50
+ # It's easier to type
84
51
  ```
85
52
 
86
- The `request` method is also aliased to `req` and `r` for better code readability if you prefer. Here are 3 equivalent routes:
53
+ Why not make the method named for the HTTP verbs? Because we want everything to be customizable. By default, calling `request` will make the route respond to every HTTP verb. If you want it to only respond to some verbs, you have several choices for how you want to do it:
87
54
 
88
55
  ```ruby
89
- request "/", "Hello World"
90
- req "/", "Hello World"
91
- r "/", "Hello World"
56
+ # If you only wanted to respond to GET, POST, and DELETE:
57
+ request "/some/path", "my_file.html.erb", respond_to: [:get, :post, :delete]
58
+ # Or this other syntax
59
+ get post delete "/some/path", "my_file.html.erb"
60
+ # Or a combination of the two
61
+ get "/some/path", "my_file.html.erb", respond_to: [:post, :delete]
62
+ # Or with dots too
63
+ get.post.delete "/some/path", "my_file.html.erb"
64
+ # Maybe mixed with request:
65
+ request.get.post.delete "/some/path", "my_file.html.erb"
66
+ # Or if you wanted to ignore those verbs and only respond to PUT and PATCH
67
+ request "/some/path", "my_file.html.erb", ignore: [:get, :post, :delete]
92
68
  ```
93
69
 
94
- For the purposes of this README, we will only use the `request` syntax, for improved readability, but anywhere in the code these are interchangeable, because they all return an `ATD::Route` object.
70
+ _N.B. See [precompilers](#precompilers) for how the file name you pass is manipulated before it is sent out._
95
71
 
96
- You could use the same basic routing syntax to return `Hello World` for a HTTP `GET` request to `/`:
72
+ You can also pass a block that will be run whenever the `Route` is matched:
97
73
 
98
74
  ```ruby
99
- request.get "/", "Hello World"
100
- ```
101
-
102
- Or you could use a simpler (but equivalent) syntax:
103
-
104
- ```ruby
105
- get "/", "Hello World"
75
+ request "/some/path", "my_file.html.erb" do
76
+ puts "Found me!"
77
+ end
106
78
  ```
107
79
 
108
- For the purposes of this README we will only use `request.get` and not `get` because we believe it adds clarity.
80
+ and if you want to, you can make the blocks return value be the output of the route by omitting `"my_file.html.erb"`. You can also manipulate the output that you passed (see [helpers](#helpers)) by modifying the `view[:raw]` variable.
109
81
 
110
- You can also have route respond to more than one HTTP verb with `get.post.put.patch`:
82
+ ## Settings
111
83
 
112
- ```ruby
113
- request.get.post.put.patch "/", "Hello World"
114
- ```
84
+ This doesn't exist yet, but is in progress in issue #29.
115
85
 
116
- This will respond to `GET`, `POST`, `PUT`, and `PATCH` with `Hello World`, but will repond to `DELETE` with a `404` status code.
86
+ ## Advanced Routing
117
87
 
118
- The `get.post.put.patch` is not recommended for readability. Instead use the following syntax:
88
+ For simple project, [the basics](#the-basics) of routing will work fine. But if you want to create a full fledged application, then you probably are going to need some of the more advanced features, such as precompilation, compilation, compiler options, and using blocks to manipulate output.
119
89
 
120
- ```ruby
121
- request "/", "Hello World", respond_to: [:get, :post, :put, :patch]
122
- ```
90
+ ### Options Hash
123
91
 
124
- Or, a simpler way would be with `:ignore`:
125
-
126
- ```ruby
127
- request "/", "Hello World", ignore: :delete
128
- ```
129
-
130
- #### Options Hash
131
-
132
- You can pass various options through the options hash. The options hash is put as the last arguemtn when creating a route. For example: `request "/", "Hello World", option: value, other-option: value`
133
-
134
- Here is a list of the option which are currently valid and default values:
92
+ The options hash is the hash provided at the end of the argument list when creating a route. For example, in `r "/", "some_file", option_A: "Value", option_B: 35` the options hash will be `{option_A: "Value", option_B: 35}`. Here is a list of options that ATD looks at internally and their default values (other keys can be passed in the options hash, they will just be passed to compilation methods):
135
93
 
136
94
  ```ruby
137
95
  status: 200 # Integer > 99 and < 1000. This will be the status code returned unless it is overridden by
138
96
  status_code: 200 # Same as status, but overrides. A slightly more verbose syntax.
139
- respond_to: nil # A list of HTTP methods as lowercase symbols which the route should respond to.
140
- ignore: nil # A list of HTTP methods as lowercase symbols which the route should not respond to.
97
+ respond_to: nil # A array of HTTP methods as lowercase symbols which the route should respond to.
98
+ ignore: nil # A array of HTTP methods as lowercase symbols which the route should not respond to. This takes highest precedence.
99
+ precompile: true # This determines if a route will be precompiled. Unless it is == false it will be precompiled.
100
+ compile: true # This determines if a route will be compiled. Unless it is == false it will be compiled.
141
101
  ```
142
102
 
143
- #### Serving Files
103
+ ### Serving Files
144
104
 
145
- All files that you want to serve must be in the `assets` directory if they are, then it is simple to just create a route, and put the filename as the output. For example this will serve `assets/index.html`:
146
-
147
- ```ruby
148
- request "/", "index.html"
149
- ```
105
+ If you want to serve files, just place them in an assets directory in the app directory and pass the file name with the file extension as the second argument to your routes. In the future you will be able to access this through the [`App` settings](#settings).
150
106
 
151
- There are certain modifications which can be made to files between the route being requested and the files being served. That information can be found in the [compilation section](#compilation).
107
+ ### Helpers
152
108
 
153
- ### Advanced Routing
109
+ In the block passed to a `Route` there are helpers available to it. You can add new helper methods by adding them to the `ATD::Helpers` module. By default there are a few defined, and here they are:
154
110
 
155
- #### DefaultApp
156
-
157
- `DefaultApp` is just another app which extends `ATD::App`, it is the app which doesn't have to be written to in `class DefaultApp`, any routes that are created in `main` will be added to `DefaultApp`, well... by default! To find out more about Apps, you can go to the [Apps section](#apps).
111
+ ```ruby
112
+ http[:request] # The Rack::Request object
113
+ http[:response] # The Rack::Response object
114
+ http[:view] # The same thing as view[:raw]
115
+ http[:method] # The HTTP verb used to access the route
116
+ http[:headers] # The headers which will be sent, by default only "content-type".
117
+ http[:status_code] # The status code which the app will respond with, by default 200
118
+ params # The params hash we all know and love.
119
+ view[:raw] # The precompiled (unless precompile:false) and compiled (unless compile:false) view.
120
+ ```
158
121
 
159
- #### Inheritance
122
+ ## Apps
160
123
 
161
- Whenever you create a route, it is given to an App Class (which inherits from `ATD::App`). This isn't apparent when you create a route in `main`, but even when you do that the route is added to `DefaultApp`. If you are using Apps, than when you create a route in the App Class, that route is given to that Class. When you start the server, it then creates an instance of the App Class and starts it as a rack app. But that is not what the purpose of Apps are.
124
+ Whenever you create a route, it is given to an `App`. This isn't apparent when you create a route in `main`, but even when you do that the route is added to `DefaultApp`, as you may remember from the [intro](#the-basics). If you are using Apps, then when you create a route in an `App`, that route belongs to the `App`. When you start the server, it then creates an instance of the `App` and starts it because it is a rack app. But that is not what the purpose of `App`s are.
162
125
 
163
- The intention of Apps are to allow you to use one App Class as a template class, from which you can create many different apps. An App Class is not a rack app all by itself. Every instance of an App Class is a rack app. But the rack app doesn't actually start until `start` is called. This means that you can create an instance of an App Class (an App), and then you can modify it before starting it. So for example, you can have an app which is impacted by an instance variable:
126
+ The intention of `Apps` are to allow you to use one `App` as a template, from which you can create many different `App`s. An `App` is not a rack app all by itself. Every instance of an App Class is a rack app. But the rack app doesn't actually start until `start` is called. This means that you can create an instance of an `App`, and then you can modify it before starting it. So for example, you can have an `App` which is impacted by an instance variable:
164
127
 
165
128
  ```ruby
166
129
  class MyApp < ATD::App
167
130
  attr_accessor :my_name
168
131
  request "/", "Hi! This is my_name's App!" do
169
- @http[:output] = @http[:output].gsub("my_name", @my_name)
132
+ view[:raw] = view[:raw].gsub("my_name", @my_name)
170
133
  end
171
134
  end
172
135
  ```
@@ -181,51 +144,47 @@ app.start
181
144
 
182
145
  Then when you try to access the website, it will respond to `/` with `Hi! This is Fredrick's App!`.
183
146
 
184
- #### Blocks
147
+ ### App Creation
185
148
 
186
- You can also add blocks to the basic routing methods to execute code when they are reached, and customize the response to the incoming request. Here is an example that will return the HTTP method used to request `/`:
149
+ To create an app you can use `ATD.new("AppName")`. It is important to note that **ATD.new is not a constructor, although I will refer to it as one**. It simply behaves like one because you can use it to "construct" a new app. The app creation process creates a new class which you can open anywhere in your app file, with the name you pass. The name must `respond_to?(:to_sym)`, and must be a valid class name. You must call the constructor before you begin adding routes to the class, or open the class at all.
150
+
151
+ You can also use the more intuitive way to create an app, which would be by declaring a class which extends `ATD::App`, like so:
187
152
 
188
153
  ```ruby
189
- request "/" do
190
- @http[:output] = @http[:method]
154
+ class MyAppClass < ATD::App
155
+ # All of my routes, settings, etc.
191
156
  end
192
157
  ```
193
158
 
194
- This example uses the `@http` variable which is provided to the route when called. `@http[:method]` is the http verb used by the request, and `@http[:output]` is what the route will return. If you have already defined a return value, `@http[:output]` will already be set to it. For example:
159
+ ### Starting the App
160
+
161
+ There are two basic ways to start the app. You can start it by calling `AppName.new.start` or more simply `AppName.start` which will create an instance and call start on it, or you can use the more common syntax. For `DefaultApp` that would be:
195
162
 
196
163
  ```ruby
197
- request "/", "The HTTP Verb is: " do
198
- @http[:output] += @http[:method]
199
- end
164
+ request "/", "Hello World!"
165
+ start
200
166
  ```
201
167
 
202
- Will return `The HTTP Verb is: get/post/put/patch/delete`, depending on the http verb used in the request. It will also pre-parse a file. So if you have added an HTML file with `request "/", "file.html"`, `@http[:output]` will be set to the contents of that file.
203
-
204
- You could also run some complex method created outside the route. This route is called in the same scope as any block you were to declare. Here is an example:
168
+ And for `MyApp` that would be:
205
169
 
206
170
  ```ruby
207
- request "/", "I'm computing a complex function" do
208
- complex_function
171
+ class MyApp < ATD::App
172
+ request "/", "Hello World!"
173
+ start
209
174
  end
210
175
  ```
211
176
 
212
- ##### @http
177
+ ## Controllers
213
178
 
214
- The `@http` instance variable can be used to work with the http request the the route is parsing. Here are some ways you can use the `@http` variable:
179
+ > **Caution:**
180
+ >
181
+ > This entire section is experimental right now and as partial or no support. Don't trust this section and tread carefully.
215
182
 
216
- ```ruby
217
- @http[:status_code] = 200 # By default the status code is 200. You can manipulate this.
218
- @http[:headers] = {} # By defualt there are no headers. You can add whatever you want.
219
- @http[:method] = env["REQUEST_METHOD"] # Writing to this does nothing. Just a way for you to see the request method.
220
- @http[:output] # This is set to the output you give in the args for request
221
- @http[:request] # The associated Rack::Request for the request.
222
- ```
223
-
224
- While you can use `@http[:status_code]` to change the status code, you can also set a status code with `r "/", status_code: 200`. That status code must be >= 100, as per the Rack Specification.
183
+ Because we understand how important it is to have flexibility in how you work, we provide support of a number of different configurations, and one of the ways we do that is with controllers. A controller is simply a module full of methods which can be referenced from a `Route` by passing `controller_name#action` instead of the file name or by putting it in the options hash with `to: controller_name#action` or `to: :action, controller: "controller_name"`.
225
184
 
226
- #### Controllers
185
+ If you want to add all the methods from one controller to an `App` you can do that by calling the `controller` method in the `App` with the name of the controller as a parameter.
227
186
 
228
- ATD also had the capabilities to use controllers. A controller is an object which responds to controller methods, usually a module with `extend self`. Here is an example of an app using controllers:
187
+ Here is an example of an `App` using controllers:
229
188
 
230
189
  ```ruby
231
190
  module MyController
@@ -237,96 +196,41 @@ end
237
196
  request "/", "MyController#test_route" #=> "You've reached test_route"
238
197
  ```
239
198
 
240
- ### Apps
241
-
242
- ATD also allows you to use different "apps". Each app is independent of the others and lives is a class with the same name as the app. A file can have any number of apps, each of which can have it's own settings, files, and routes. By default, adding routes to `main` will make them a part of the app `DefaultApp`, which will work fine if you only need one app.
199
+ ## Compilation
243
200
 
244
- #### App Creation
201
+ _N.B. At some point in the indefinite future there might (but probably will) be a [tilt](https://github.com/rtomayko/tilt) integration_
245
202
 
246
- To create an app you can use `ATD.new("AppName")`. It is important to note that *ATD.new is not a constructor, although I will refer to it as one*. It simply behaves like one because you can use it to "construct" a new app. The app creation process creates a new class which you can open anywhere in your app file, with the name you pass. The name must `#respond_to?(:to_sym)`, and must be a valid class name. You must call the constructor before you begin adding routes to the class, or open the class at all.
203
+ ### The Basics
247
204
 
248
- You can also use the more intuitive way to create an app, which would be by declaring a class which extends `ATD::App`, like so:
205
+ Because `ATD` attempts to practice separation of concerns, there is a special module, `ATD::Compilation` which is responsible for dealing with compilation. In `ATD` there are two types of compilers: First, there are precompilers, which run during the apps startup process and do things like minify assets and compile things which do not need to by dynamic. Second, there are compilers, which deal with dynamic assets and run whenever a `Route` is reached. To create them you can use the following syntax (the example is for compiling ERB files):
249
206
 
250
207
  ```ruby
251
- class MyAppClass < ATD::App
252
- # All of my routes, code, etc.
208
+ to_compile "erb" do |file = "", *opts|
209
+ ERB.new(file).result
253
210
  end
254
211
  ```
255
212
 
256
- #### Starting the App
257
-
258
- There are two basic ways to start the app. You can start it by calling `AppName.new.start` or more simply `AppName.start` which will create an instance and call start on it, or you can use the more common syntax. For `DefaultApp` that would be:
213
+ Or if you wanted to define a precompiler to remove all the newlines from a JS file:
259
214
 
260
215
  ```ruby
261
- request "/", "Hello World!"
262
- start
263
- ```
264
-
265
- And for `MyApp` that would be:
266
-
267
- ```ruby
268
- class MyApp
269
- request "/", "Hello World!"
270
- start
216
+ to_precompile "js" do |file = "", *opts|
217
+ file.gsub("\n", "")
271
218
  end
272
219
  ```
273
220
 
274
- #### App Routing
221
+ If you notices, the precompilers and compilers take the contents of the file as the first argument (`file`) and they take the compiler options as the second argument (`opts`). The options are just whatever was passed with the route, for example in `r "/", "my_file.erb", hi: true` `opts == {hi: true}`.
275
222
 
276
- To add routes to an app, you simply must call the standard routing method inside the app class. For example, to create and add routes to an app called `Name` I would use the following code:
223
+ > **Under the Hood**
224
+ >
225
+ > The compilation works by going through the file extensions from last to first and running the compilations for each extension in that order. For example `file.html.erb` will first be compiled by the "erb" compiler, then the output of the "erb" compiler will be compiled by the "html" compiler.
277
226
 
278
- ```ruby
279
- class Name < ATD::App
280
- request "/", "Hello World"
281
- end
282
- ```
227
+ ### Precompilation
283
228
 
284
- ### Logging
285
-
286
- Currently there is no specified logging interface. Just use `puts` or `p` to log to `STDOUT`.
229
+ > Precompilers are not sufficiently advanced to get their own special section yet, but will be soon.
287
230
 
288
231
  ### Compilation
289
232
 
290
- `ATD` will take your views and compress them for you so that your information can be transmitted more quickly. There are two different types of compilation, precompilation which occurs when the server is started, and compilation, which will live compile your code. While there are a few compilers and precompilers which will come with `ATD` most are user defined. To define compiler and precompiler methods simply add them like so:
291
-
292
- ```ruby
293
- module ATD::Compilation::Precomiler
294
- def extension(file, *opts) # This will work with any file that has .extension
295
- # File is the contents of the file being compiled
296
- # Whatever you return is what the file will be compiled to
297
- file
298
- end
299
- end
300
-
301
- module ATD::Compilation::Compiler
302
- # Same thing here
303
- def extension(file, *opts) # This will work with any file that has .extension
304
- file
305
- end
306
- end
307
- ```
308
-
309
- The compilation works by going through the file extensions from last to first and running the compilations for each extension in that order. For example `file.html.erb` will first be compiled by the ERB compiler, then the output of the ERB compiler will be compiled by the HTML compiler.
310
-
311
- When you include a file with then `request "/route", "file.ext` it will be precompiled, but not compiled. If you want live compilation there are a few options. If you want live compilation and precompilation you will have to use the following syntax:
312
-
313
- ```ruby
314
- request "/", "file.ext" do
315
- live_compilation_method @http[:output]
316
- end
317
- ```
318
-
319
- If you don't need precompilation and just want live compilation (the ususal use case) then you can just use the following format:
320
-
321
- ```ruby
322
- request "/" do
323
- live_compilation_method File.read("/assets/file.ext")
324
- end
325
- ```
326
-
327
- ## Design Paradigms
328
-
329
- ATD is designed to fit into many different design paradigms, and to allow each person to adopt their own styles while leaving code readable to everyone. Do do this, the code was left fairly unstructured, but [here are a few examples from well known frameworks](https://gitlab.com/izwick-schachter/atd-examples.git).
233
+ > Compilers are not sufficiently advanced to get their own special section yet, but will be soon.
330
234
 
331
235
  ## Documentation
332
236
 
@@ -336,12 +240,16 @@ You can find the YARD docs at http://izwick-schachter.gitlab.io/atd/YARD/.
336
240
 
337
241
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
338
242
 
339
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
243
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to the gem page on [rubygems.org](https://rubygems.org/gems/atd).
244
+
245
+ ### Some notes about Semantic Versioning
246
+
247
+ [Semantic versioning]() is pretty nice. I like it. But, I really only want to hit a major version when it's production ready, so for now will we will follow semver in that bugfix releases (x.x.*) will be backwards compatible, but minor versions will not be. As soon as this is production ready we will hit 1.0.0 and start using semver.
340
248
 
341
249
  ## Contributing
342
250
 
343
- Bug reports and pull requests are welcome on GitLab at https://gitlab.com/izwick-schachter/atd/issues. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
251
+ [Bug reports](https://gitlab.com/izwick-schachter/atd/issues) and [merge requests](https://gitlab.com/izwick-schachter/atd/merge_requests) are welcome on GitLab. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
344
252
 
345
- ### Contribution Policies
253
+ ## Contribution Policies
346
254
 
347
- Every constribution should correspond to a relevent issue which you keep up to date with notes on what you are working on. Each issues gets branched off of development and is named issue/<issue number>. When you are ready to merege it back in, make sure it passes both rubocop and all the tests. Each issue should get additional test if necessary.
255
+ Every contribution should correspond to a relevant issue which you keep up to date with notes on what you are working on. Each issues gets branched off of development and is named `issue/#{issue_number}`. When you are ready to merge it back in, make sure it passes both rubocop and all the tests. Each issue should get additional tests if necessary.
@@ -22,9 +22,12 @@ Gem::Specification.new do |spec|
22
22
  spec.bindir = "exe"
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
+ spec.required_ruby_version = '>= 2.2.2'
26
+ spec.required_rubygems_version = '>= 2.6.4'
25
27
 
26
28
  spec.add_runtime_dependency "rack", "~> 2.0"
27
29
  spec.add_runtime_dependency "webrick", "~> 1.3"
30
+ spec.add_runtime_dependency "mime-types", "~> 3.0"
28
31
 
29
32
  spec.add_development_dependency "bundler", "~> 1.12"
30
33
  spec.add_development_dependency "rake", "~> 10.0"
@@ -34,4 +37,6 @@ Gem::Specification.new do |spec|
34
37
  spec.add_development_dependency "yard", "~> 0.9"
35
38
  spec.add_development_dependency "rack-test", "~> 0.6"
36
39
  spec.add_development_dependency "simplecov", "~> 0.12"
40
+ spec.add_development_dependency "guard", "~> 2.14"
41
+ spec.add_development_dependency "guard-minitest", "~> 2.4"
37
42
  end
data/lib/atd.rb CHANGED
@@ -1,13 +1,17 @@
1
1
  require_relative "atd/version"
2
2
  require "rack"
3
3
  require "webrick"
4
+ require "mime-types"
5
+ require_relative "atd/internal_helpers"
4
6
  require_relative "atd/builtin_class_modifications"
7
+ require_relative "atd/internal_helpers"
5
8
  require_relative "atd/routes"
6
9
  # Extension packs
7
10
  # require_relative "extensions/precompilers"
8
11
 
9
12
  # The assistant technical director of your website. It does the dirty work so you can see the big picture.
10
13
  module ATD
14
+ using Refinements
11
15
  # Creates a new ATD App based on the template of {ATD::App}.
12
16
  # @return [ATD::App]
13
17
  # @param [Symbol] name The name of the new app and new class generated.
@@ -20,11 +24,16 @@ module ATD
20
24
  # So called because each instance stores a route, and will be called if that route is reached.
21
25
  # A route for the purposes of {ATD} is a parser that will be fed env in {ATD::App#call the rack app}.
22
26
  class Route
23
- attr_accessor :args, :method, :block, :path, :output, :app, :actions, :status_code
27
+ include InternalHelpers
28
+
29
+ attr_reader :app
30
+ attr_accessor :args, :method, :block, :path, :output, :actions, :status_code, :filename, :headers
24
31
 
25
32
  # The first two arguments must me the path and the output.
26
33
  def initialize(*args, &block)
34
+ @args, @block, @path, @output, @actions, @status_code, @filename = nil
27
35
  @status_code = 200
36
+ @headers = {}
28
37
  @method = [:get, :post, :put, :patch, :delete]
29
38
  @method = [] if args.last.is_a?(Hash) && !(args.last[:respond_to].nil? || args.last[:ignore].nil?)
30
39
  @app = :DefaultApp
@@ -71,6 +80,7 @@ module ATD
71
80
  # the variables up through the different method calls.
72
81
  if args.first.is_a?(ATD::Route)
73
82
  @method = args.first.method
83
+ @filename = args.first.filename
74
84
  @output = args.first.output
75
85
  @path = args.first.path
76
86
  @args = args.first.args
@@ -85,67 +95,65 @@ module ATD
85
95
  end
86
96
  end
87
97
 
88
- # Converts an instance of {ATD::Route} into it's Hash representation.
89
- # The format for the Hash is listed {ATD::App#initialize here}
90
- # @api private
91
- def to_h
92
- routes = {}
93
- routes[@path] = {}
94
- routes[@path][@method] = {}
95
- routes[@path][@method] = {
96
- status_code: @status_code,
97
- output: @output,
98
- block: @block,
99
- args: @args,
100
- route: self
101
- }
102
- routes
103
- end
104
-
105
98
  private
106
99
 
107
100
  # This should also manage @method at some point
108
- def parse_args(*args, &block)
109
- args.compact!
110
- args.flatten!
111
- args.reject! { |arg| arg.is_a?(File) || arg.is_a?(Proc) || arg ? false : arg.empty? } # File doesn't respond to empty
112
- @block = block
113
- # This requires the format ATD::Route.new(path, route, args)
114
- @path ||= args.shift
115
- @output ||= args.shift
116
- @args = Array(@args).concat(args) unless args.nil?
101
+ def parse_args(path = "", output = "", *args, &block)
102
+ args = Hash(args[0]) if args.is_a? Array
103
+ if output.is_a? Hash
104
+ args.merge(output)
105
+ output = ""
106
+ end
107
+ @args = Hash(@args).merge(args)
108
+ # rubocop:disable Style/EmptyElse
109
+ if !path.is_a? ATD::Route
110
+ @block = block
111
+ @path = path
112
+ @output = output
113
+ else
114
+ # Maybe here it would make sense to assign path and output into args
115
+ end
116
+ # rubocop:enable Style/EmptyElse
117
117
  # @output should be whatever the input is unless the input is a controller/action or the input is_file_string?
118
118
  if @output =~ /^\w*#\w*$/ # Check if @path is a controller#action combo
119
119
  controller, action = @output.split("#")
120
120
  @action = Object.const_get(controller.to_sym).method(action.to_sym)
121
121
  @output = @action.call
122
122
  end
123
- # TODO: Choose one! They all work... I think...
124
- # Method 1:
125
- target_location = []
126
- caller_locations.each do |caller_location|
127
- target_dir = File.dirname(caller_location.absolute_path.to_s)
128
- target_location.push(target_dir) unless target_dir.include?(__dir__)
129
- end
130
- # Method 2:
131
- target_location = caller_locations.reject do |caller_location|
132
- File.dirname(caller_location.absolute_path.to_s).include? __dir__
133
- end
134
- output_full_path = "#{File.dirname(target_location[0].absolute_path)}/assets/#{@output}"
123
+ # These next few lines are working on the assumption that if the file exists we want it, and the precompiler is
124
+ # counting on that.
125
+ output_full_path = asset @output
135
126
  @output = File.new(output_full_path) if File.exist?(output_full_path) && !Dir.exist?(output_full_path)
136
- if args.is_a?(Hash) || args.last.is_a?(Hash)
137
- @method += Array(args.last[:respond_to]) unless args.last[:respond_to].nil?
138
- @method -= Array(args.last[:ignore]) unless args.last[:ignore].nil?
139
- @status_code = args.last[:status] unless args.last[:status].nil?
140
- @status_code = args.last[:status_code] unless args.last[:status_code].nil?
141
- end
127
+ @method += Array(args[:respond_to]) unless args[:respond_to].nil?
128
+ @method -= Array(args[:ignore]) unless args[:ignore].nil?
129
+ @status_code = args[:status] unless args[:status].nil?
130
+ @status_code = args[:status_code] unless args[:status_code].nil?
131
+ Compilation.precompile(self)
132
+ @headers["content-type"] = MIME::Types.of(@filename)[0].to_s unless @filename.nil?
133
+ @block = @actions unless @actions.nil?
142
134
  self
143
135
  end
144
136
  end
145
137
 
138
+ module Helpers
139
+ def http
140
+ { request: @request, response: @response, view: @view, method: @method, headers: @headers, status_code: @status_code }
141
+ end
142
+
143
+ def params
144
+ @request.params
145
+ end
146
+
147
+ def view
148
+ @view
149
+ end
150
+ end
151
+
146
152
  # A template {App} that all Apps extend. When a new App is created with {ATD.new ATD.new} it extends this class.
147
153
  class App
148
- attr_accessor :http
154
+ include Helpers
155
+ include Compilation
156
+
149
157
  class << self
150
158
  attr_accessor :routes # An array of instances of {ATD::Route} that belong to this {App}.
151
159
 
@@ -192,30 +200,15 @@ module ATD
192
200
  # }
193
201
  # @param [Array] routes An array of instances of {ATD::Route}.
194
202
  def initialize(routes = [])
195
- @routes = {}
196
- Array(routes + self.class.routes).each do |route|
197
- route = route.clone
198
- filename = ATD::Compilation.precompile(route, (route.args.last.is_a?(Hash) ? route.args.last[:precompile] : nil))
199
- route_hash = route.to_h
200
- current_route = route_hash[route.path][route.method]
201
- current_route[:filename] = filename
202
- block = current_route[:block]
203
- # An instance method must be defined from the block make it the same as the controller actions. We don't want to
204
- # convert the controller actions to blocks because if we did that, we would have to take them out of scope to allow
205
- # them to use the @http variables.
206
- current_route[:block] = define_singleton_method(block.object_id.to_s.tr("0-9", "a-j").to_sym, &block) unless block.nil?
207
- current_route[:block] = route.actions unless route.actions.nil?
208
- @routes = @routes.to_h.deep_merge(route_hash)
203
+ @routes = (routes + Array(self.class.routes)).each do |route|
204
+ Compilation.precompile(route) unless route.output.is_a? Hash
209
205
  end
210
206
  end
211
207
 
212
208
  # Allows instance method route creation. Just another way of creating routes.
213
209
  def request(*args, &block)
214
210
  route = ATD::Route.new(*args, &block)
215
- filename = ATD::Compilation.precompile(route, (route.args.last.is_a?(Hash) ? route.args.last[:precompile] : nil))
216
- route_hash = route.to_h
217
- route_hash[route.path][route.method][:filename] = filename
218
- @routes = @routes.to_h.deep_merge(route_hash)
211
+ @routes += Array(route)
219
212
  route
220
213
  end
221
214
  alias req request
@@ -232,27 +225,29 @@ module ATD
232
225
  # It will return status code 200 and whatever output corresponds the that route if it exists, and if it doesn't
233
226
  # it will return status code 404 and the message "Error 404"
234
227
  def call(env)
235
- @http = nil
236
- route = route(env)
237
- return error(404) if route.nil?
238
- route[:output] = Compilation.compile(route[:filename], route[:output]) unless !route[:args].nil? && !route[:args].empty? && route[:args][0].is_a?(Hash) && route[:args][0][:compile] == false
239
- return [route[:status_code].to_i, Hash(route[:headers]), Array(route[:output])] if route[:block].nil?
240
- http output: route[:output], request: Rack::Request.new(env), method: env["REQUEST_METHOD"], response: Rack::Response.new(env)
241
- return_val = method(route[:block]).call
242
- @http[:output] = return_val if @http[:output].nil?
243
- [@http[:status_code].to_i, Hash(@http[:headers]), Array(@http[:output])]
228
+ routes = @routes.where(path: env["PATH_INFO"], method: env["REQUEST_METHOD"].downcase.to_sym)
229
+ warn "WARNING: Multiple routes matched the request" if routes.length > 1
230
+ route = routes.first
231
+ return error 404 if route.nil?
232
+ output = route.output
233
+ output = Compilation.compile(route)[:content] unless route.args[:compile] == false
234
+ return [route.status_code.to_i, Hash(route.headers), Array(output)] if route.block.nil?
235
+ generate_variables(env, route)
236
+ return_val = instance_eval(&route.block) if route.block.is_a? Proc
237
+ return_val = method(route.block).call if route.block.is_a? Method
238
+ @view[:raw] = return_val if @view[:raw].nil? || @view[:raw].empty?
239
+ [@status_code.to_i, Hash(@headers), Array(@view[:raw])]
244
240
  end
245
241
 
246
242
  private
247
243
 
248
- def route(env)
249
- return nil if @routes[env["PATH_INFO"]].nil?
250
- # return @routes[env["PATH_INFO"]][[]] unless @routes[env["PATH_INFO"]][[]].nil?
251
- @routes[env["PATH_INFO"]].include_in_key?(env["REQUEST_METHOD"].downcase.to_sym)
252
- end
253
-
254
- def http(additional_params)
255
- @http = { status_code: 200, headers: {} }.merge(additional_params)
244
+ def generate_variables(env, route)
245
+ @status_code = 200
246
+ @headers = {}
247
+ @view = { raw: route.output }
248
+ @request = Rack::Request.new(env)
249
+ @method = env["REQUEST_METHOD"]
250
+ @response = Rack::Response.new(env)
256
251
  end
257
252
 
258
253
  def error(number)
@@ -264,7 +259,7 @@ end
264
259
 
265
260
  # @return [ATD::Route]
266
261
  def request(*args, &block)
267
- ATD::App.request(args, block)
262
+ ATD::App.request(*args, &block)
268
263
  end
269
264
  alias req request
270
265
  alias r request
@@ -1,38 +1,45 @@
1
- # @!visibility private
2
- class Hash
3
- # Not only merges two hashes, but also merges the hashes that may be nested in.
4
- #
5
- # For example:
6
- # {a: {b: "c"}}
7
- # Is a nested hash
8
- def deep_merge(second)
9
- merger = proc do |_, v1, v2|
10
- if v1.is_a?(Hash) && v2.is_a?(Hash) then v1.merge(v2, &merger)
11
- elsif v1.is_a?(Array) && v2.is_a?(Array) then v1 | v2
12
- elsif [:undefined, nil, :nil].include?(v2) then v1
13
- else v2
1
+ module ATD::Refinements
2
+ # @!visibility private
3
+ refine Hash do
4
+ # Not only merges two hashes, but also merges the hashes that may be nested in.
5
+ #
6
+ # For example:
7
+ # {a: {b: "c"}}
8
+ # Is a nested hash
9
+ def deep_merge(second)
10
+ merger = proc do |_, v1, v2|
11
+ if v1.is_a?(Hash) && v2.is_a?(Hash) then v1.merge(v2, &merger)
12
+ elsif v1.is_a?(Array) && v2.is_a?(Array) then v1 | v2
13
+ elsif [:undefined, nil, :nil].include?(v2) then v1
14
+ else v2
15
+ end
14
16
  end
17
+ merge(second.to_h, &merger)
15
18
  end
16
- merge(second.to_h, &merger)
17
- end
18
19
 
19
- def include_in_key?(search)
20
- each do |key, val|
21
- return val if key.is_a?(Array) && key.include?(search)
20
+ def include_in_key?(search)
21
+ each do |key, val|
22
+ return val if key.is_a?(Array) && key.include?(search)
23
+ end
22
24
  end
23
25
  end
24
- end
25
26
 
26
- # This method only exists for the test suite, specifically {ATDTest#test_route_creation}.
27
- # @!visibility private
28
- class Object
29
- # Checks if two objects are instances of the same class and that they have the same instance variables
30
- def same_properties_as?(other_class)
31
- other_class.class == self.class && class_instance_variables == other_class.class_instance_variables
27
+ # This method only exists for the test suite, specifically {ATDTest#test_route_creation}.
28
+ # @!visibility private
29
+ refine Object do
30
+ # Returns the instance variables of a class
31
+ def class_instance_variables
32
+ instance_variables.map { |var| [var, instance_variable_get(var)] }.to_h
33
+ end
32
34
  end
33
35
 
34
- # Returns the instance variables of a class
35
- def class_instance_variables
36
- instance_variables.map { |var| [var, instance_variable_get(var)] }.to_h
36
+ refine Array do
37
+ def where(criteria)
38
+ select do |element|
39
+ criteria.all? do |criterion, expected_value|
40
+ Array(element.public_send(criterion)).include?(expected_value)
41
+ end
42
+ end
43
+ end
37
44
  end
38
45
  end
@@ -0,0 +1,9 @@
1
+ module InternalHelpers
2
+ def asset(name)
3
+ return "" if name.to_s.empty?
4
+ target_location = caller_locations.reject do |caller_location|
5
+ File.dirname(caller_location.absolute_path.to_s).include? File.expand_path("..", __dir__)
6
+ end
7
+ "#{File.dirname(target_location[0].absolute_path)}/assets/#{name}"
8
+ end
9
+ end
@@ -1,60 +1,59 @@
1
-
2
1
  module ATD
3
2
  # This module holds everything related to the compilation of routes.
4
3
  module Compilation
5
- # A module designed to hold all the precompilation methods
6
- module Precompiler
7
- extend self
4
+ include InternalHelpers
8
5
 
9
- # Lists all filestypes that have defined precompiler methods
10
- def filetypes
11
- instance_methods(true) - [:filetypes]
12
- end
6
+ def respond_to_missing?(method, include_private = false)
7
+ ATD::Compilation.compilers.key?(method.to_sym) || super
13
8
  end
14
9
 
15
- # A module designed to hold all the compilation methods
16
- module Compiler
17
- extend self
18
-
19
- # Lists all file extentions which have defined compiler methods
20
- def filetypes
21
- instance_methods(true) - [:filetypes]
22
- end
10
+ def method_missing(method, *args, &block)
11
+ return super unless ATD::Compilation.compilers.key?(method.to_sym)
12
+ filename = args.pop
13
+ ATD::Compilation.compilers[method].call(ATD::Compilation.parse(ATD::Compilation.compilers, filename, File.read(asset(filename)), Hash(args).merge(run: false)), *args)
23
14
  end
24
15
 
25
- # This method is responsible for live compilation. It takes an ATD::Route as input, and returns either
26
- # the filename if Route.output is a file or the Route.output string if Route.output is a string.
27
- # It will also take the file and call the corresponding compilation method on it.
28
- def self.compile(name, contents)
29
- return contents if name.nil?
30
- contents = File.read(contents) if contents.is_a? File
31
- parse(Compiler, name, contents)
32
- end
16
+ class << self
17
+ attr_accessor :compilers, :precompilers
33
18
 
34
- # This method is responsible for precompilation. It takes an ATD::Route as input, and returns either
35
- # the filename if Route.output is a file or the Route.output string if Route.output is a string.
36
- # It will also take the file and call the corresponding precompilation method on it.
37
- # route.output is either a full, expanded file path, a file, or a string
38
- def self.precompile(route, *opts)
39
- return nil if route.output.nil?
40
- if route.output.is_a?(File)
41
- name = route.output.is_a?(File) ? File.basename(route.output) : route.output.dup
42
- file = route.output.is_a?(File) ? route.output.dup : File.new(route.output)
43
- route.output = parse(Precompiler, name, File.read(file)) if opts[0].nil? || opts[0]
44
- return name
19
+ # This method is responsible for live compilation. It takes an ATD::Route as input, and returns either
20
+ # the filename if Route.output is a file or the Route.output string if Route.output is a string.
21
+ # It will also take the file and call the corresponding compilation method on it.
22
+ def compile(route, *opts)
23
+ { content: parse(@compilers, route.filename, route.output, opts), name: route.filename }
45
24
  end
46
- route.output
47
- end
48
25
 
49
- class << self
50
- private
26
+ # This method is responsible for precompilation. It takes an ATD::Route as input and compilation options. It
27
+ # sets the routes filename and output properties to be the filename and the contents of that file or, if route.output
28
+ # doesn't correspond to a valid file, it sets both to be route.output. It returns the precompiled version of the file
29
+ # regardless, and sets route.ouput to be the precompiled version unless the opt precompile: false is passed.
30
+ def precompile(route, *opts)
31
+ opts = Hash(opts[0]) unless opts.is_a? Hash
32
+ opts = opts.merge(route.args)
33
+ # route.output should always be a String, File, or NilClass
34
+ name = route.output.to_s
35
+ name = File.basename(route.output) if route.output.is_a?(File)
36
+ # name should always be a String
37
+ content = if route.output.is_a?(File) || (route.output.is_a?(String) && !route.output.empty? && File.exist?(route.output) && !Dir.exist?(route.output))
38
+ File.read(route.output)
39
+ elsif !route.output.nil?
40
+ route.output
41
+ else
42
+ ""
43
+ end
44
+ # content should always be a String
45
+ route.output = parse(@precompilers, name, content, opts) unless opts[:precompile] == false
46
+ route.filename = name
47
+ { content: parse(@precompilers, name, content, opts), name: route.filename }
48
+ end
51
49
 
52
- def parse(type, name, contents)
53
- name = name.split(".")
54
- extensions = name - [name.first]
50
+ def parse(type, name, contents, *opts)
51
+ return contents if name.include?("\n")
52
+ extensions = name.to_s.split(".")
53
+ extensions.shift
55
54
  extensions.each do |extension|
56
- if type.filetypes.include? extension.to_sym
57
- contents = type.send(extension, contents)
55
+ if type.key? extension.to_sym
56
+ contents = type[extension.to_sym].call(contents, *opts) if Hash(opts.last)[:run] != false
58
57
  extensions -= [extension]
59
58
  end
60
59
  end
@@ -64,6 +63,19 @@ module ATD
64
63
  end
65
64
  end
66
65
 
66
+ ATD::Compilation.compilers = {}
67
+ ATD::Compilation.precompilers = {}
68
+
69
+ def to_compile(name, &block)
70
+ warn "You have overriden the #{name.to_sym} compiler" if ATD::Compilation.compilers.key? name.to_sym
71
+ ATD::Compilation.compilers[name.to_sym] = block
72
+ end
73
+
74
+ def to_precompile(name, &block)
75
+ warn "You have overriden the #{name.to_sym} precompiler" if ATD::Compilation.compilers.key? name.to_sym
76
+ ATD::Compilation.precompilers[name.to_sym] = block
77
+ end
78
+
67
79
  class Object
68
- include ATD::Compilation::Compiler
80
+ include ATD::Compilation
69
81
  end
@@ -1,3 +1,3 @@
1
1
  module ATD
2
- VERSION = "0.4.0".freeze # The current version number of {ATD}.
2
+ VERSION = "0.5.0".freeze # The current version number of {ATD}.
3
3
  end
@@ -4,6 +4,6 @@ module ATD::Compilation::Precompiler
4
4
  end
5
5
 
6
6
  def css(file)
7
- file.gsub(/[\s\n]/, "")
7
+ file
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ACecretMaster
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-06 00:00:00.000000000 Z
11
+ date: 2017-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mime-types
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +164,34 @@ dependencies:
150
164
  - - "~>"
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0.12'
167
+ - !ruby/object:Gem::Dependency
168
+ name: guard
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.14'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.14'
181
+ - !ruby/object:Gem::Dependency
182
+ name: guard-minitest
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '2.4'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '2.4'
153
195
  description:
154
196
  email:
155
197
  - izwick.schachter@gmail.com
@@ -164,6 +206,7 @@ files:
164
206
  - CHANGELOG
165
207
  - CODE_OF_CONDUCT.md
166
208
  - Gemfile
209
+ - Guardfile
167
210
  - LICENSE
168
211
  - README.md
169
212
  - Rakefile
@@ -172,6 +215,7 @@ files:
172
215
  - bin/setup
173
216
  - lib/atd.rb
174
217
  - lib/atd/builtin_class_modifications.rb
218
+ - lib/atd/internal_helpers.rb
175
219
  - lib/atd/routes.rb
176
220
  - lib/atd/version.rb
177
221
  - lib/extensions/precompilers.rb
@@ -188,15 +232,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
188
232
  requirements:
189
233
  - - ">="
190
234
  - !ruby/object:Gem::Version
191
- version: '0'
235
+ version: 2.2.2
192
236
  required_rubygems_version: !ruby/object:Gem::Requirement
193
237
  requirements:
194
238
  - - ">="
195
239
  - !ruby/object:Gem::Version
196
- version: '0'
240
+ version: 2.6.4
197
241
  requirements: []
198
242
  rubyforge_project:
199
- rubygems_version: 2.5.2
243
+ rubygems_version: 2.6.10
200
244
  signing_key:
201
245
  specification_version: 4
202
246
  summary: The assistant technical director of your website. It does the dirty work