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.
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 "" )