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
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Robert Feldt
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Swagr
|
2
|
+
|
3
|
+
Simple web app skeleton creator for Ruby based on Sinatra, Slim, Coffeescript and d3.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install yourself as:
|
8
|
+
|
9
|
+
$ gem install swagr
|
10
|
+
|
11
|
+
or add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'swagr'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Create a new Swagr web app:
|
22
|
+
|
23
|
+
swagr create <dir>
|
24
|
+
|
25
|
+
and then start the web app with:
|
26
|
+
|
27
|
+
ruby <dir>/app.rb
|
28
|
+
|
29
|
+
and then access your web app by opening the browser and going to 0.0.0.0:4000. The default web app has a few simple examples of how to transfer data from a back-end, long-running Ruby computation and display it with a small amount of Coffeescript code that uses d3 to visualise progress. Very rudimentary for now.
|
30
|
+
|
31
|
+
After you have checked the default example you can add/removed/modify slim files in <dir>/views and coffescript files in <dir>/coffee and add your own backend and access it from app.rb.
|
32
|
+
|
33
|
+
NOTE! This is very early times and most things in Swagr are likely to change. There is no real "design" yet, I needed to get something up and running quickly.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/swagr
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'thor'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
require 'swagr'
|
7
|
+
|
8
|
+
SkeletonDir = "skeletons"
|
9
|
+
DefaultSkeletonTarball = "bootstrap_default_130109_0842.zip"
|
10
|
+
SkeletonTarball = File.join(SkeletonDir, DefaultSkeletonTarball)
|
11
|
+
|
12
|
+
StaticDir = PublicDir = "static"
|
13
|
+
DefaultSubdirs = ["coffee", "views", StaticDir]
|
14
|
+
|
15
|
+
class SwagrCommand < Thor
|
16
|
+
|
17
|
+
include Thor::Actions
|
18
|
+
|
19
|
+
def self.source_root
|
20
|
+
File.dirname(__FILE__).split("/")[0...-1].join("/")
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "update", "Update all templates by re-downloading the latest versions of needed libs"
|
24
|
+
def update
|
25
|
+
inside File.join("templates", PublicDir, "js") do
|
26
|
+
get "http://d3js.org/d3.v3.min.js"
|
27
|
+
get "http://coffeescript.org/extras/coffee-script.js"
|
28
|
+
get "http://code.jquery.com/jquery-latest.js"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
#desc "run DIR", "Run a swagr app in DIR, only things that override the base app needs to be in DIR"
|
33
|
+
#def run(dir)
|
34
|
+
#end
|
35
|
+
|
36
|
+
desc "init DIR", "Create a skeleton structure in DIR for customizing a swagr app"
|
37
|
+
def init(dir)
|
38
|
+
empty_directory dir
|
39
|
+
inside dir do
|
40
|
+
DefaultSubdirs.each {|subdir| empty_directory(subdir)}
|
41
|
+
end
|
42
|
+
# Now copy everything in the templates dir into the
|
43
|
+
directory "templates", dir
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "create DIR", "Create/setup a swagr app in DIR"
|
47
|
+
def create(dir)
|
48
|
+
init(dir)
|
49
|
+
public_dir = File.join(dir, PublicDir)
|
50
|
+
tarball = File.join(public_dir, "skeleton.zip")
|
51
|
+
copy_file SkeletonTarball, tarball
|
52
|
+
inside public_dir do
|
53
|
+
run "unzip skeleton.zip"
|
54
|
+
FileUtils.rm_rf "skeleton.zip"
|
55
|
+
say "Removed tarball"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
SwagrCommand.start
|
@@ -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 "" )
|