swagr 0.0.5
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +1 -0
- data/bin/swagr +61 -0
- data/examples/examples01/app.rb +104 -0
- data/examples/examples01/brownian_searcher.rb +93 -0
- data/examples/examples01/coffee/application.coffee +0 -0
- data/examples/examples01/coffee/d3graph.coffee +54 -0
- data/examples/examples01/coffee/updating_text_graph.coffee +43 -0
- data/examples/examples01/coffee/updating_top_list.coffee +39 -0
- data/examples/examples01/static/css/bootstrap.css +6103 -0
- data/examples/examples01/static/css/bootstrap.min.css +868 -0
- data/examples/examples01/static/css/style.css +15 -0
- data/examples/examples01/static/img/glyphicons-halflings-white.png +0 -0
- data/examples/examples01/static/img/glyphicons-halflings.png +0 -0
- data/examples/examples01/static/js/bootstrap.js +2170 -0
- data/examples/examples01/static/js/bootstrap.min.js +7 -0
- data/examples/examples01/static/js/coffee-script.js +8 -0
- data/examples/examples01/static/js/d3.v3.min.js +4 -0
- data/examples/examples01/static/js/jquery-latest.js +9472 -0
- data/examples/examples01/views/application.slim +0 -0
- data/examples/examples01/views/footer.slim +2 -0
- data/examples/examples01/views/index.slim +80 -0
- data/examples/examples01/views/layout.slim +26 -0
- data/examples/examples01/views/navbar.slim +16 -0
- data/lib/swagr.rb +5 -0
- data/lib/swagr/version.rb +3 -0
- data/skeletons/bootstrap_default_130109_0842.zip +0 -0
- data/swagr.gemspec +26 -0
- data/templates/app.rb +104 -0
- data/templates/brownian_searcher.rb +93 -0
- data/templates/coffee/application.coffee +0 -0
- data/templates/coffee/d3graph.coffee +54 -0
- data/templates/coffee/updating_text_graph.coffee +43 -0
- data/templates/coffee/updating_top_list.coffee +39 -0
- data/templates/static/css/style.css +15 -0
- data/templates/static/js/coffee-script.js +8 -0
- data/templates/static/js/d3.v3.min.js +4 -0
- data/templates/static/js/jquery-latest.js +9472 -0
- data/templates/views/footer.slim +2 -0
- data/templates/views/index.slim +80 -0
- data/templates/views/layout.slim +26 -0
- data/templates/views/navbar.slim +16 -0
- metadata +173 -0
File without changes
|
@@ -0,0 +1,80 @@
|
|
1
|
+
h1 Hello, Swagr world!
|
2
|
+
|
3
|
+
/! Example A: Some static svg
|
4
|
+
div class="container"
|
5
|
+
svg width="400" height="50"
|
6
|
+
text x="0" y="35" font-family="Helvetica" font-size="20" SVG:
|
7
|
+
circle cx="175" cy="25" r="20" fill="rgba(128, 0, 128, 1.0)"
|
8
|
+
circle cx="200" cy="25" r="20" fill="rgba(0, 0, 255, 0.75)"
|
9
|
+
circle cx="225" cy="25" r="20" fill="rgba(0, 255, 0, 0.5)"
|
10
|
+
circle cx="250" cy="25" r="20" fill="rgba(255, 255, 0, 0.25)"
|
11
|
+
circle cx="275" cy="25" r="20" fill="rgba(255, 0, 0, 0.1)"
|
12
|
+
/! rect x="100" y="100" width="100" height="50"
|
13
|
+
/! ellipse cx="150" cy="80" rx="50" ry="25" fill="red"
|
14
|
+
/! line x1="0" y1="0" x2="300" y2="50" stroke="black"
|
15
|
+
|
16
|
+
/! Example B: Using d3 to add elements dynamically
|
17
|
+
script src="js/d3.v3.min.js"
|
18
|
+
div class="container" id="d3a"
|
19
|
+
div class="container" id="d3b"
|
20
|
+
script type="text/javascript"
|
21
|
+
| var co = d3.select("#d3a");
|
22
|
+
co.append("p").text("Hello, d3.js world!");
|
23
|
+
|
24
|
+
/! Example C: Coffee-script inline and importing from other files
|
25
|
+
script src="js/coffee-script.js"
|
26
|
+
script src="coffee/d3graph.js"
|
27
|
+
script src="coffee/updating_text_graph.js"
|
28
|
+
script src="coffee/updating_top_list.js"
|
29
|
+
div class="container" id="d3_from_coffeescript"
|
30
|
+
div class="container" id="updating_text"
|
31
|
+
div class="container" id="top_list_header"
|
32
|
+
text class="update"
|
33
|
+
div class="container" id="top_list"
|
34
|
+
script type="text/coffeescript"
|
35
|
+
| a = 1.5
|
36
|
+
b = 2 + a
|
37
|
+
str = "Hello, coffee-script and d3.js world! b = " + b + ". And now an updating text graph from Coffee-script..."
|
38
|
+
console.log(str)
|
39
|
+
d3.select("#d3_from_coffeescript").append("span").text(str)
|
40
|
+
root = exports ? window
|
41
|
+
|
42
|
+
# 1. Updating row of random int values
|
43
|
+
g = new root.Swagr.UpdatingTextGraph "#updating_text", "/data/randints/arrayofsize6.json", {
|
44
|
+
width: 500
|
45
|
+
height: 120
|
46
|
+
}
|
47
|
+
g.run_updates_for(5)
|
48
|
+
#g.start_updates()
|
49
|
+
|
50
|
+
|
51
|
+
# 2. Updating top list of positions found by BrownianSearcher
|
52
|
+
|
53
|
+
class MyUpdatingTopList extends root.Swagr.UpdatingToplist
|
54
|
+
_join_data: (data) ->
|
55
|
+
@svg.selectAll("text").data(data.top_list, (d, i) -> d.number)
|
56
|
+
|
57
|
+
textmapper: (elems, data) -> (d,i) ->
|
58
|
+
(i+1).toString() + ". dist: " + d.distance.toFixed(2) +
|
59
|
+
" pos: (" + d.x + ", " + d.y + ")" +
|
60
|
+
" step: " + d.number
|
61
|
+
|
62
|
+
g2 = new MyUpdatingTopList "#top_list", "/data/brownian_search/search_info.json", {
|
63
|
+
width: 500
|
64
|
+
height: 160
|
65
|
+
transition_y: 100
|
66
|
+
update_interval: 3.0
|
67
|
+
transition_time: 1000
|
68
|
+
}
|
69
|
+
#g2.run_updates_for(20)
|
70
|
+
g2.start_updates()
|
71
|
+
|
72
|
+
# 3. Header for the top list, showing current number of steps.
|
73
|
+
update_header = () ->
|
74
|
+
d3.json("/data/brownian_search/current_position.json", (error, data) ->
|
75
|
+
pos_str = "(" + data[0].x + ", " + data[0].y + ")"
|
76
|
+
d3.select("#top_list_header").select("text")
|
77
|
+
.text("Top 5 positions at step: " + data[0].number + ", pos: " + pos_str)
|
78
|
+
)
|
79
|
+
update_header()
|
80
|
+
setInterval(update_header, g2.opts.update_interval * 1000)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
meta charset="utf-8"
|
5
|
+
title Swagr web app template
|
6
|
+
meta name="viewport" content="width=device-width, initial-scale=1.0"
|
7
|
+
meta name="description" content=""
|
8
|
+
meta name="author" content="FIXME: YOUR NAME"
|
9
|
+
link href="css/bootstrap.css" rel="stylesheet" /! media="screen"
|
10
|
+
link href="css/style.css" rel="stylesheet" /! media="screen"
|
11
|
+
style
|
12
|
+
| body {
|
13
|
+
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
|
14
|
+
}
|
15
|
+
body
|
16
|
+
|
17
|
+
div class="container"
|
18
|
+
== slim :navbar
|
19
|
+
== yield
|
20
|
+
|
21
|
+
div class="container"
|
22
|
+
div id="footer"
|
23
|
+
== slim :footer
|
24
|
+
|
25
|
+
script src="js/jquery-latest.js"
|
26
|
+
script src="js/bootstrap.min.js"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
div class="navbar navbar-inverse navbar-fixed-top"
|
2
|
+
div class="navbar-inner"
|
3
|
+
div class="container"
|
4
|
+
a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"
|
5
|
+
span class="icon-bar"
|
6
|
+
span class="icon-bar"
|
7
|
+
span class="icon-bar"
|
8
|
+
a class="brand" href="#" SwagrApp
|
9
|
+
div class="nav-collapse collapse"
|
10
|
+
ul class="nav"
|
11
|
+
li class="active"
|
12
|
+
a href="#" Home
|
13
|
+
li
|
14
|
+
a href="about" About
|
15
|
+
li
|
16
|
+
a href="contact" Contact
|
data/lib/swagr.rb
ADDED
Binary file
|
data/swagr.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'swagr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "swagr"
|
8
|
+
gem.version = Swagr::VERSION
|
9
|
+
gem.authors = ["Robert Feldt"]
|
10
|
+
gem.email = ["robert.feldt@gmail.com"]
|
11
|
+
gem.description = %q{simple web app gui framework for ruby}
|
12
|
+
gem.summary = %q{Simple, barebones web app gui framework for Ruby programs/processes. Based on Sinatra, Sass, Slim, Coffeescript and d3.js. Geared to interfacing with long-running Ruby processes.}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency('thor')
|
21
|
+
gem.add_dependency('slim')
|
22
|
+
gem.add_dependency('sass')
|
23
|
+
gem.add_dependency('coffee-script')
|
24
|
+
gem.add_dependency('json')
|
25
|
+
|
26
|
+
end
|
data/templates/app.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'slim'
|
4
|
+
require 'coffee-script'
|
5
|
+
require 'json'
|
6
|
+
require 'feldtruby'
|
7
|
+
|
8
|
+
class CoffeeEngine < Sinatra::Base
|
9
|
+
|
10
|
+
set :views, File.dirname(__FILE__) + '/coffee'
|
11
|
+
|
12
|
+
get "/coffee/*.js" do
|
13
|
+
filename = params[:splat].first
|
14
|
+
coffee filename.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
get "/js/*.coffee" do
|
18
|
+
filename = params[:splat].first
|
19
|
+
coffee filename.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
require File.join(".", File.dirname(__FILE__), "brownian_searcher")
|
25
|
+
BrownianSearcher = BrownianMotion2DSearch.new(5, 2.0)
|
26
|
+
Thread.new {BrownianSearcher.start_search}
|
27
|
+
|
28
|
+
# The data engine should return the json or csv formatted data
|
29
|
+
# that is used in your app. You need to set this up to dynamically deliver the
|
30
|
+
# latest data about your running Ruby app/process/server.
|
31
|
+
class DataEngine < Sinatra::Base
|
32
|
+
module SendAsJson
|
33
|
+
def json_response(data)
|
34
|
+
content_type :json
|
35
|
+
data.to_json
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
helpers SendAsJson
|
40
|
+
|
41
|
+
# Static data files are served from the "/data" dir
|
42
|
+
set :views, File.dirname(__FILE__) + '/data'
|
43
|
+
|
44
|
+
# Example handler that returns a random json data set for all example requests...
|
45
|
+
get %r{/data/randints/arrayofsize([\d]+).json} do |size|
|
46
|
+
a = Array.new(size.to_i).map {-5+(10*rand()).to_i}
|
47
|
+
#json_response a.uniq.sort
|
48
|
+
b = a.uniq.sort.map {|v| {"value" => v}}
|
49
|
+
json_response b
|
50
|
+
end
|
51
|
+
|
52
|
+
get '/data/brownian_search/search_info.json' do
|
53
|
+
json_response BrownianSearcher.search_info
|
54
|
+
end
|
55
|
+
|
56
|
+
get '/data/brownian_search/current_position.json' do
|
57
|
+
json_response [BrownianSearcher.pos]
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class WAppGuiServer < Sinatra::Base
|
63
|
+
|
64
|
+
enable :logging
|
65
|
+
disable :dump_errors
|
66
|
+
|
67
|
+
use DataEngine
|
68
|
+
use CoffeeEngine
|
69
|
+
|
70
|
+
set :views, File.dirname(__FILE__) + '/views'
|
71
|
+
set :public_folder, File.dirname(__FILE__) + '/static'
|
72
|
+
|
73
|
+
# get '/example_data.json' do
|
74
|
+
# content_type :json
|
75
|
+
# [-1, 2, 4, -7, -9].to_json
|
76
|
+
# end
|
77
|
+
|
78
|
+
get '/' do
|
79
|
+
slim :index
|
80
|
+
end
|
81
|
+
|
82
|
+
def copyright_holders
|
83
|
+
"FIXME: Your Name(s)..."
|
84
|
+
end
|
85
|
+
|
86
|
+
# Set the first year you started working on this app. The copyright will then go from then to current year.
|
87
|
+
def first_year
|
88
|
+
2012
|
89
|
+
end
|
90
|
+
|
91
|
+
def year
|
92
|
+
current_year = Time.now.year
|
93
|
+
if current_year != first_year
|
94
|
+
"#{first_year}-#{current_year}"
|
95
|
+
else
|
96
|
+
"#{first_year}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
if __FILE__ == $0
|
103
|
+
WAppGuiServer.run! :port => 4000
|
104
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
# Our dummy long-running ruby process is a Brownian motion where
|
4
|
+
# we are interested in the max x and y values found. Our GUI
|
5
|
+
# will present a top list of the top 3 results found so far.
|
6
|
+
class BrownianMotion2DSearch
|
7
|
+
Pos = Struct.new("Pos", :x, :y, :number)
|
8
|
+
class Pos
|
9
|
+
Origo = Pos.new(0,0,-1)
|
10
|
+
|
11
|
+
def distance_to(pos)
|
12
|
+
Math.sqrt( (pos.x - self.x)**2 + (pos.y - self.y)**2 )
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_json(*a)
|
16
|
+
{
|
17
|
+
'x' => x,
|
18
|
+
'y' => y,
|
19
|
+
'number' => number,
|
20
|
+
'distance' => self.distance_to(Origo)
|
21
|
+
}.to_json(*a)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :num_steps, :pos
|
26
|
+
|
27
|
+
def initialize(maxToplistLength = 10, maxSleepSeconds = 2.0)
|
28
|
+
@max_top_list_length = maxToplistLength
|
29
|
+
@num_steps = 0
|
30
|
+
@max_sleep_seconds = maxSleepSeconds
|
31
|
+
@pos = new_pos(0, 0)
|
32
|
+
@top_list = [@pos]
|
33
|
+
end
|
34
|
+
|
35
|
+
def new_pos(x, y)
|
36
|
+
Pos.new(x, y, @num_steps)
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_search
|
40
|
+
while true
|
41
|
+
take_brownian_step()
|
42
|
+
puts "Step = #{@num_steps}, at pos = #{@pos}"
|
43
|
+
update_top_list()
|
44
|
+
# Sleep so we are not searching all the time => simulate other long-running calcs...
|
45
|
+
sleep( @max_sleep_seconds * rand() )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return info about the search such as the number of steps taken and the current top list
|
50
|
+
def search_info
|
51
|
+
{"step" => @num_steps, "top_list" => @top_list}
|
52
|
+
end
|
53
|
+
|
54
|
+
def rand_unit_step
|
55
|
+
rand() < 0.495 ? -1 : 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def take_brownian_step
|
59
|
+
@num_steps += 1
|
60
|
+
@pos = new_pos(@pos.x + rand_unit_step(), @pos.y + rand_unit_step())
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_top_list
|
64
|
+
@top_list << @pos
|
65
|
+
sort_top_list
|
66
|
+
limit_top_list_length
|
67
|
+
end
|
68
|
+
|
69
|
+
def sort_top_list
|
70
|
+
# Add a small value based on when the position was found => older positions with same distance
|
71
|
+
# are ranked higher than later found positions with same distance.
|
72
|
+
@top_list = @top_list.sort_by {|p| -p.distance_to(Pos::Origo)+(p.number/(1000.0*@num_steps))}
|
73
|
+
end
|
74
|
+
|
75
|
+
def limit_top_list_length
|
76
|
+
@top_list = @top_list.take(@max_top_list_length)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if __FILE__ == $0
|
81
|
+
s = BrownianMotion2DSearch.new
|
82
|
+
Thread.new {s.start_search}
|
83
|
+
require 'pp'
|
84
|
+
Thread.new {
|
85
|
+
while true
|
86
|
+
sleep 5.0
|
87
|
+
pp s.search_info
|
88
|
+
end
|
89
|
+
}
|
90
|
+
sleep 20
|
91
|
+
pp s.search_info
|
92
|
+
exit -1
|
93
|
+
end
|
File without changes
|
@@ -0,0 +1,54 @@
|
|
1
|
+
root = exports ? window
|
2
|
+
root.Swagr = if root.Swagr then root.Swagr else {}
|
3
|
+
class root.Swagr.D3Graph
|
4
|
+
default_options =
|
5
|
+
width: 960
|
6
|
+
height: 500
|
7
|
+
update_interval: 1.5 # seconds
|
8
|
+
transition_y: 50
|
9
|
+
transition_x: 50
|
10
|
+
transition_time: 750 # milliseconds
|
11
|
+
|
12
|
+
constructor: (@selector, @dataUrl, opts = {}) ->
|
13
|
+
@opts = @set_default_options_unless_given(opts, default_options)
|
14
|
+
@_append_elements()
|
15
|
+
@update() # First update so we have something to show...
|
16
|
+
|
17
|
+
set_default_options_unless_given: (givenOpts, defaultOpts) ->
|
18
|
+
for own option, value of defaultOpts
|
19
|
+
givenOpts[option] = value unless givenOpts.hasOwnProperty(option)
|
20
|
+
givenOpts
|
21
|
+
|
22
|
+
# Append the svg to the top-level selector
|
23
|
+
_append_elements: () ->
|
24
|
+
@svg = d3.select(@selector).append("svg")
|
25
|
+
.attr("width", @opts.width)
|
26
|
+
.attr("height", @opts.height)
|
27
|
+
.append("g")
|
28
|
+
.attr("transform", @_transform_string())
|
29
|
+
|
30
|
+
# Start updating the graph
|
31
|
+
start_updates: ->
|
32
|
+
@interval_id = root.setInterval ( => @update() ), @opts.update_interval*1000
|
33
|
+
|
34
|
+
# Stop updating the graph
|
35
|
+
stop_updates: -> root.clearInterval(@interval_id)
|
36
|
+
|
37
|
+
# Run the updates for a given number of seconds
|
38
|
+
run_updates_for: (seconds) ->
|
39
|
+
@start_updates()
|
40
|
+
setTimeout ( => @stop_updates(); console.log("Stopped updating graph!"); ), seconds*1000
|
41
|
+
|
42
|
+
update: ->
|
43
|
+
d3.json(@dataUrl, (error, data) =>
|
44
|
+
@data = data
|
45
|
+
# D3's general update pattern:
|
46
|
+
# 1. DATA JOIN - Join new data with old elements, if any, then update, enter and exit below.
|
47
|
+
@elems = @_join_data(data)
|
48
|
+
# 2. UPDATE - Update existing elements as needed.
|
49
|
+
@_update_existing_elements()
|
50
|
+
# 3. ENTER - Create new elements as needed.
|
51
|
+
@_enter_new_elements()
|
52
|
+
# 4. EXIT - Remove old elements as needed.
|
53
|
+
@_remove_exiting_elements()
|
54
|
+
)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
root = exports ? window
|
2
|
+
root.Swagr = if root.Swagr then root.Swagr else {}
|
3
|
+
|
4
|
+
# Assumes d3graph.coffee has been required before this one...
|
5
|
+
class root.Swagr.UpdatingTextGraph extends root.Swagr.D3Graph
|
6
|
+
_transform_string: -> "translate(16," + (@opts.height / 2) + ")"
|
7
|
+
|
8
|
+
_join_data: (data) ->
|
9
|
+
@svg.selectAll("text").data(data, (d) -> d.value)
|
10
|
+
|
11
|
+
_update_existing_elements: (text) ->
|
12
|
+
@elems.attr("class", "update")
|
13
|
+
.text(@textmapper(@elems, @data))
|
14
|
+
.transition()
|
15
|
+
.duration(@opts.transition_time)
|
16
|
+
.attr("x", @xmapper(@elems, @data))
|
17
|
+
|
18
|
+
_enter_new_elements: (text) ->
|
19
|
+
@elems.enter().append("text")
|
20
|
+
.attr("class", "enter")
|
21
|
+
.attr("dy", ".35em")
|
22
|
+
.attr("y", -@opts.transition_y)
|
23
|
+
.attr("x", @xmapper(@elems, @data))
|
24
|
+
.style("fill-opacity", 1e-6)
|
25
|
+
.text(@textmapper(@elems, @data))
|
26
|
+
.transition()
|
27
|
+
.duration(@opts.transition_time)
|
28
|
+
.attr("y", 0)
|
29
|
+
.style("fill-opacity", 1)
|
30
|
+
|
31
|
+
_remove_exiting_elements: (text) ->
|
32
|
+
@elems.exit()
|
33
|
+
.attr("class", "exit")
|
34
|
+
.transition()
|
35
|
+
.duration(@opts.transition_time)
|
36
|
+
.attr("y", @opts.transition_y)
|
37
|
+
.style("fill-opacity", 1e-6)
|
38
|
+
.remove()
|
39
|
+
|
40
|
+
xmapper: (elems, data) -> (d,i) -> i * 70
|
41
|
+
|
42
|
+
textmapper: (elems, data) -> (d,i) ->
|
43
|
+
d.value + ( if (i+1 isnt data.length) then "," else "" )
|