tgauge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/TGauge.gemspec +40 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/tgauge.rb +119 -0
- data/lib/app/controllers/flash.rb +90 -0
- data/lib/app/controllers/session.rb +27 -0
- data/lib/app/controllers/tcontroller_base.rb +91 -0
- data/lib/app/models/associatable.rb +117 -0
- data/lib/app/models/searchable.rb +47 -0
- data/lib/app/models/trecord_base.rb +163 -0
- data/lib/db/db_connection.rb +99 -0
- data/lib/db/router.rb +74 -0
- data/lib/db/server.rb +27 -0
- data/lib/db/show_exceptions.rb +62 -0
- data/lib/db/static_viewer.rb +52 -0
- data/lib/db/templates/error_page.html.erb +62 -0
- data/lib/templates/Gemfile +3 -0
- data/lib/templates/config/routes.rb +7 -0
- data/lib/templates/db/seeds.rb +7 -0
- data/lib/tgauge.rb +16 -0
- data/lib/version.rb +3 -0
- metadata +180 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require_relative 'searchable'
|
3
|
+
require_relative 'associatable'
|
4
|
+
require_relative '../../db/db_connection'
|
5
|
+
|
6
|
+
module TGauge
|
7
|
+
class TRecordBase
|
8
|
+
extend Associatable
|
9
|
+
extend Searchable
|
10
|
+
|
11
|
+
def self.my_attr_accessor(*names)
|
12
|
+
names.each do |name|
|
13
|
+
define_method(name) do
|
14
|
+
instance_variable_get("@" + name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
define_method(name.to_s + "=") do |arg|
|
18
|
+
instance_variable_set("@" + name.to_s, arg)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.my_attr_reader(*names)
|
24
|
+
names.each do |name|
|
25
|
+
define_method(name) do
|
26
|
+
instance_variable_get("@" + name.to_s)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.columns
|
32
|
+
# ...
|
33
|
+
return @columns if @columns
|
34
|
+
|
35
|
+
arr = DBConnection.execute(<<-SQL)
|
36
|
+
SELECT
|
37
|
+
*
|
38
|
+
FROM
|
39
|
+
#{self.table_name}
|
40
|
+
SQL
|
41
|
+
|
42
|
+
@columns = []
|
43
|
+
arr.nfields.times do |i|
|
44
|
+
@columns << arr.fname(i)
|
45
|
+
end
|
46
|
+
|
47
|
+
@columns
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.finalize!
|
51
|
+
columns.each do |column|
|
52
|
+
inst_var = "@" + column.to_s
|
53
|
+
define_method(column) do
|
54
|
+
attributes[column]
|
55
|
+
end
|
56
|
+
define_method(column.to_s + "=") do |arg|
|
57
|
+
attributes[column] = arg
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.table_name=(table_name)
|
63
|
+
@table_name = table_name
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.table_name
|
67
|
+
@table_name = @table_name || self.to_s.tableize
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.all
|
71
|
+
objs_arr = DBConnection.execute(<<-SQL)
|
72
|
+
SELECT
|
73
|
+
#{table_name}.*
|
74
|
+
FROM
|
75
|
+
#{table_name}
|
76
|
+
SQL
|
77
|
+
|
78
|
+
parse_all(objs_arr)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.parse_all(results)
|
82
|
+
# ...
|
83
|
+
results.map { |obj_hash| self.new(obj_hash) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.find(id)
|
87
|
+
obj = DBConnection.execute(<<-SQL, id)
|
88
|
+
SELECT
|
89
|
+
#{table_name}.*
|
90
|
+
FROM
|
91
|
+
#{table_name}
|
92
|
+
WHERE
|
93
|
+
#{table_name}.id = ?
|
94
|
+
SQL
|
95
|
+
|
96
|
+
parse_all(obj).first
|
97
|
+
end
|
98
|
+
|
99
|
+
def initialize(params = {})
|
100
|
+
# ...
|
101
|
+
params.each do |att_name, val|
|
102
|
+
att_name = att_name.to_sym
|
103
|
+
raise "unknown attribute '#{att_name}'" unless columns.include?(att_name.to_s)
|
104
|
+
self.send(att_name.to_s + "=", val)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def attributes
|
109
|
+
@attributes ||= {}
|
110
|
+
end
|
111
|
+
|
112
|
+
def attribute_values
|
113
|
+
attributes.values
|
114
|
+
end
|
115
|
+
|
116
|
+
def insert
|
117
|
+
cols = columns.reject { |col| col == "id" }
|
118
|
+
attr_count = cols.count
|
119
|
+
column_str = cols.join(", ")
|
120
|
+
quest_str = Array.new(attr_count) {"?"}.join(", ")
|
121
|
+
debugger
|
122
|
+
DBConnection.execute(<<-SQL, attribute_values)
|
123
|
+
INSERT INTO
|
124
|
+
#{table_name} (#{column_str})
|
125
|
+
VALUES
|
126
|
+
(#{quest_str})
|
127
|
+
SQL
|
128
|
+
end
|
129
|
+
|
130
|
+
def update
|
131
|
+
attr_count = columns.count - 1
|
132
|
+
column_str = columns[1..-1].map { |col| "#{col} = ?" }.join(", ")
|
133
|
+
|
134
|
+
DBConnection.execute(<<-SQL, attribute_values)
|
135
|
+
UPDATE
|
136
|
+
#{table_name}
|
137
|
+
SET
|
138
|
+
#{column_str}
|
139
|
+
WHERE
|
140
|
+
id = ?
|
141
|
+
SQL
|
142
|
+
end
|
143
|
+
|
144
|
+
def save
|
145
|
+
if attributes[:id]
|
146
|
+
update
|
147
|
+
else
|
148
|
+
insert
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def columns
|
155
|
+
self.class.columns
|
156
|
+
end
|
157
|
+
|
158
|
+
def table_name
|
159
|
+
self.class.table_name
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'pg'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
PRINT_QUERIES = ENV['PRINT_QUERIES'] == 'true'
|
6
|
+
MIGRATION_FILES = Dir.glob("./db/migrations/*.sql").to_a
|
7
|
+
|
8
|
+
module TGauge
|
9
|
+
class DBConnection
|
10
|
+
def self.app_name
|
11
|
+
YAML.load_file(Dir.pwd + '/config/database.yml')['database']
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.open
|
15
|
+
@postgres = PG::Connection.new(
|
16
|
+
dbname: app_name,
|
17
|
+
port: 5432
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.migrate
|
22
|
+
ensure_migrations_table
|
23
|
+
|
24
|
+
MIGRATION_FILES.each do |file|
|
25
|
+
filename = file.match(/([\w|-]*)\.sql$/)[1]
|
26
|
+
|
27
|
+
unless migrated_files.include?(filename)
|
28
|
+
instance.exec(File.read(file))
|
29
|
+
instance.exec(<<-SQL)
|
30
|
+
INSERT INTO
|
31
|
+
migrations (filename)
|
32
|
+
VALUES
|
33
|
+
('#{filename}')
|
34
|
+
SQL
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.instance
|
40
|
+
open if @db.nil?
|
41
|
+
|
42
|
+
@db
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.execute(*args)
|
46
|
+
print_query(*args)
|
47
|
+
instance.execute(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.last_insert_row_id
|
51
|
+
instance.last_insert_row_id
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.reset
|
55
|
+
commands = [
|
56
|
+
"dropdb #{app_name}",
|
57
|
+
"createdb #{app_name}"
|
58
|
+
]
|
59
|
+
commands.each { |command| `#{command}` }
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def self.ensure_migrations_table
|
65
|
+
table = instance.exec(<<-SQL)
|
66
|
+
SELECT to_regclass('migrations') AS exists
|
67
|
+
SQL
|
68
|
+
|
69
|
+
unless table[0]['exists']
|
70
|
+
instance.exec(<<-SQL)
|
71
|
+
CREATE_TABLE migrations(
|
72
|
+
id SERIAL PRIMARY KEY,
|
73
|
+
filename VARCHAR(255) NOT NULL
|
74
|
+
)
|
75
|
+
SQL
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.migrated_files
|
80
|
+
Set.new instance.exec(<<-SQL).values.flatten
|
81
|
+
SELECT
|
82
|
+
filename
|
83
|
+
FROM
|
84
|
+
migrations
|
85
|
+
SQL
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.print_query(query, *interpolation_args)
|
89
|
+
return unless PRINT_QUERIES
|
90
|
+
|
91
|
+
puts '--------------------'
|
92
|
+
puts query
|
93
|
+
unless interpolation_args.empty?
|
94
|
+
puts "interpolate: #{interpolation_args.inspect}"
|
95
|
+
end
|
96
|
+
puts '--------------------'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/db/router.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
class Route
|
2
|
+
attr_reader :pattern, :http_method, :controller_class, :action_name
|
3
|
+
|
4
|
+
def initialize(pattern, http_method, controller_class, action_name)
|
5
|
+
@pattern = pattern
|
6
|
+
@http_method = http_method
|
7
|
+
@controller_class = controller_class
|
8
|
+
@action_name = action_name
|
9
|
+
end
|
10
|
+
|
11
|
+
# checks if pattern matches path and method matches request method
|
12
|
+
def matches?(req)
|
13
|
+
(@pattern =~ req.path) == 0 && req.request_method.downcase == @http_method.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
# use pattern to pull out route params (save for later?)
|
17
|
+
# instantiate controller and call controller action
|
18
|
+
def run(req, res)
|
19
|
+
match_data = @pattern.match(req.path)
|
20
|
+
route_params = {}
|
21
|
+
match_data.names.each do |key|
|
22
|
+
route_params[key] = match_data[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
controller = controller_class.new(req, res, route_params)
|
26
|
+
controller.invoke_action(@action_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Router
|
31
|
+
attr_reader :routes
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@routes = []
|
35
|
+
end
|
36
|
+
|
37
|
+
# simply adds a new route to the list of routes
|
38
|
+
def add_route(pattern, method, controller_class, action_name)
|
39
|
+
@routes << Route.new(pattern, method, controller_class, action_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# evaluate the proc in the context of the instance
|
43
|
+
# for syntactic sugar :)
|
44
|
+
def draw(&proc)
|
45
|
+
self.instance_eval(&proc)
|
46
|
+
end
|
47
|
+
|
48
|
+
# make each of these methods that
|
49
|
+
# when called add route
|
50
|
+
[:get, :post, :put, :delete].each do |http_method|
|
51
|
+
define_method(http_method) do |path, controller, action_name|
|
52
|
+
add_route(path, http_method, controller, action_name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# should return the route that matches this request
|
57
|
+
def match(req)
|
58
|
+
@routes.each do |route|
|
59
|
+
return route if route.matches?(req)
|
60
|
+
end
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# either throw 404 or call run on a matched route
|
65
|
+
def run(req, res)
|
66
|
+
route = match(req)
|
67
|
+
if route
|
68
|
+
route.run(req, res)
|
69
|
+
else
|
70
|
+
res.write("Route #{req.path} could not be found.")
|
71
|
+
res.status = 404
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/db/server.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require_relative './config/routes'
|
3
|
+
require_relative 'lib/static_viewer'
|
4
|
+
require_relative 'lib/show_exceptions'
|
5
|
+
|
6
|
+
module TGauge
|
7
|
+
class Server
|
8
|
+
def self.start
|
9
|
+
app = Proc.new do |env|
|
10
|
+
req = Rack::Request.new(env)
|
11
|
+
res = Rack::Response.new
|
12
|
+
ROUTER.run(req, res)
|
13
|
+
res.finish
|
14
|
+
end
|
15
|
+
|
16
|
+
full_app = Rack::Builder.new do
|
17
|
+
use ShowExceptions
|
18
|
+
use StaticViewer
|
19
|
+
run app
|
20
|
+
end.to_app
|
21
|
+
|
22
|
+
Rack::Server.start({
|
23
|
+
app: full_app,
|
24
|
+
Port: 3000
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'byebug'
|
3
|
+
|
4
|
+
class ShowExceptions
|
5
|
+
attr_reader :app
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
app.call(env)
|
13
|
+
rescue Exception => e
|
14
|
+
render_exception(e)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def render_exception(e)
|
20
|
+
dir_path = File.dirname(__FILE__)
|
21
|
+
template_fname = File.join(dir_path, "templates", "error_page.html.erb")
|
22
|
+
template = File.read(template_fname)
|
23
|
+
body = ERB.new(template).result(binding)
|
24
|
+
|
25
|
+
['500', {'Content-type' => 'text/html'}, [body]]
|
26
|
+
end
|
27
|
+
|
28
|
+
def error_source_file(e)
|
29
|
+
stack_trace_top(e)[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
def stack_trace_top(e)
|
33
|
+
e.backtrace[0].split(':')
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_formatted_source(e)
|
37
|
+
source_file_name = error_source_file(e)
|
38
|
+
source_line_num = source_line_num(e)
|
39
|
+
source_lines = extract_source(source_file_name)
|
40
|
+
format_source(source_lines, source_line_num)
|
41
|
+
end
|
42
|
+
|
43
|
+
def source_line_num(e)
|
44
|
+
stack_trace_top(e)[1].to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
def formatted_source(file, source_line_num)
|
48
|
+
source_lines = extract_source(file)
|
49
|
+
format_source(source_lines, source_line_num)
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_source(file)
|
53
|
+
source_file = File.open(file, 'r')
|
54
|
+
source_file.readlines
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_source(source_lines, source_line_num)
|
58
|
+
start = [0, source_line_num - 3].max
|
59
|
+
lines = source_lines[start..(start + 5)]
|
60
|
+
Hash[*(start+1..(lines.count + start)).zip(lines).flatten]
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
class StaticViewer
|
5
|
+
FILE_TYPE = {
|
6
|
+
'jpg' => 'image/jpeg',
|
7
|
+
'zip' => 'application/zip',
|
8
|
+
'png' => 'image/png',
|
9
|
+
'txt' => 'text/plain'
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
if is_public?(env)
|
18
|
+
req = Rack::Request.new(env)
|
19
|
+
res = Rack::Response.new
|
20
|
+
|
21
|
+
file_path = "#{req.path}"
|
22
|
+
build_response(res, file_path)
|
23
|
+
else
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def build_response(res, file_path)
|
30
|
+
type = find_content_type(file_path)
|
31
|
+
|
32
|
+
if type && Pathname(file_path).exist?
|
33
|
+
res.write(File.open(file_path, 'r') { |f| (f.read) })
|
34
|
+
res['Content-Type'] = find_content_type(file_path)
|
35
|
+
res.finish
|
36
|
+
else
|
37
|
+
type ? res.write("Could not find file.") : res.write("Unsupported file type")
|
38
|
+
res.status = 404
|
39
|
+
res['Content-Type'] = 'text/html'
|
40
|
+
res.finish
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def is_public?(env)
|
45
|
+
!!(Regexp.new("/public/*") =~ env["PATH_INFO"])
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_content_type(path)
|
49
|
+
type = path.match(/\.(\w*)/)[1]
|
50
|
+
FILE_TYPE[type]
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<head>
|
2
|
+
<style>
|
3
|
+
.header {
|
4
|
+
font-family: sans-serif;
|
5
|
+
background: #d00;
|
6
|
+
padding: 10px;
|
7
|
+
color: #fff;
|
8
|
+
}
|
9
|
+
|
10
|
+
.source-view {
|
11
|
+
border: 10px solid lightgray;
|
12
|
+
padding: 5px;
|
13
|
+
width: 800px;
|
14
|
+
}
|
15
|
+
|
16
|
+
.line {
|
17
|
+
white-space: pre-wrap;
|
18
|
+
font-family: sans-serif;
|
19
|
+
padding: 2px 0;
|
20
|
+
}
|
21
|
+
|
22
|
+
.line-num {
|
23
|
+
background: lightgray;
|
24
|
+
padding: 2px;
|
25
|
+
border-top: 1px solid #aaa;
|
26
|
+
}
|
27
|
+
|
28
|
+
.line.error {
|
29
|
+
background: #d00;
|
30
|
+
color: #fff;
|
31
|
+
}
|
32
|
+
</style>
|
33
|
+
</head>
|
34
|
+
|
35
|
+
<body>
|
36
|
+
<h2 class='header'><%=e.class%>: <%=e.message%></h2>
|
37
|
+
|
38
|
+
<h4>Extracted source (around line <b><%=source_line_num(e)%></b>):</h4>
|
39
|
+
|
40
|
+
<div class='source-view'>
|
41
|
+
<table cellpadding="0" cellspacing="0">
|
42
|
+
<% extract_formatted_source(e).each do |line_num, line| %>
|
43
|
+
<tr>
|
44
|
+
<td>
|
45
|
+
<pre class='line-num'><%=line_num %></pre>
|
46
|
+
</td>
|
47
|
+
<td>
|
48
|
+
<pre class='line
|
49
|
+
<%= 'error' if line_num == source_line_num(e)%>'><%= line %></pre>
|
50
|
+
</td>
|
51
|
+
</tr>
|
52
|
+
<% end %>
|
53
|
+
</table>
|
54
|
+
</div>
|
55
|
+
<h5><%= File.expand_path(error_source_file(e)) %></h5>
|
56
|
+
|
57
|
+
<h3>Stack trace</h3>
|
58
|
+
<% e.backtrace.each do |stack_line| %>
|
59
|
+
<%= stack_line %>
|
60
|
+
<br>
|
61
|
+
<% end %>
|
62
|
+
</body>
|
data/lib/tgauge.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module TGauge
|
2
|
+
end
|
3
|
+
|
4
|
+
require_relative './version.rb'
|
5
|
+
|
6
|
+
require_relative './app/models/trecord_base'
|
7
|
+
require_relative './app/controllers/tcontroller_base'
|
8
|
+
|
9
|
+
Dir.glob('./app/models/*.rb') { |file| require file }
|
10
|
+
Dir.glob('./app/controllers/*.rb') { |file| require file }
|
11
|
+
|
12
|
+
require './db/seeds'
|
13
|
+
|
14
|
+
require_relative './db/db_connection'
|
15
|
+
require_relative './db/router'
|
16
|
+
require_relative './db/server'
|
data/lib/version.rb
ADDED