swagr 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +33 -0
  5. data/Rakefile +1 -0
  6. data/bin/swagr +61 -0
  7. data/examples/examples01/app.rb +104 -0
  8. data/examples/examples01/brownian_searcher.rb +93 -0
  9. data/examples/examples01/coffee/application.coffee +0 -0
  10. data/examples/examples01/coffee/d3graph.coffee +54 -0
  11. data/examples/examples01/coffee/updating_text_graph.coffee +43 -0
  12. data/examples/examples01/coffee/updating_top_list.coffee +39 -0
  13. data/examples/examples01/static/css/bootstrap.css +6103 -0
  14. data/examples/examples01/static/css/bootstrap.min.css +868 -0
  15. data/examples/examples01/static/css/style.css +15 -0
  16. data/examples/examples01/static/img/glyphicons-halflings-white.png +0 -0
  17. data/examples/examples01/static/img/glyphicons-halflings.png +0 -0
  18. data/examples/examples01/static/js/bootstrap.js +2170 -0
  19. data/examples/examples01/static/js/bootstrap.min.js +7 -0
  20. data/examples/examples01/static/js/coffee-script.js +8 -0
  21. data/examples/examples01/static/js/d3.v3.min.js +4 -0
  22. data/examples/examples01/static/js/jquery-latest.js +9472 -0
  23. data/examples/examples01/views/application.slim +0 -0
  24. data/examples/examples01/views/footer.slim +2 -0
  25. data/examples/examples01/views/index.slim +80 -0
  26. data/examples/examples01/views/layout.slim +26 -0
  27. data/examples/examples01/views/navbar.slim +16 -0
  28. data/lib/swagr.rb +5 -0
  29. data/lib/swagr/version.rb +3 -0
  30. data/skeletons/bootstrap_default_130109_0842.zip +0 -0
  31. data/swagr.gemspec +26 -0
  32. data/templates/app.rb +104 -0
  33. data/templates/brownian_searcher.rb +93 -0
  34. data/templates/coffee/application.coffee +0 -0
  35. data/templates/coffee/d3graph.coffee +54 -0
  36. data/templates/coffee/updating_text_graph.coffee +43 -0
  37. data/templates/coffee/updating_top_list.coffee +39 -0
  38. data/templates/static/css/style.css +15 -0
  39. data/templates/static/js/coffee-script.js +8 -0
  40. data/templates/static/js/d3.v3.min.js +4 -0
  41. data/templates/static/js/jquery-latest.js +9472 -0
  42. data/templates/views/footer.slim +2 -0
  43. data/templates/views/index.slim +80 -0
  44. data/templates/views/layout.slim +26 -0
  45. data/templates/views/navbar.slim +16 -0
  46. metadata +173 -0
@@ -0,0 +1,2 @@
1
+ h3 Footer...
2
+ | Copyright © #{year} #{copyright_holders}
@@ -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
@@ -0,0 +1,5 @@
1
+ require "swagr/version"
2
+
3
+ module Swagr
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,3 @@
1
+ module Swagr
2
+ VERSION = "0.0.5"
3
+ end
@@ -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
@@ -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 "" )