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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in wapp.gemspec
4
+ gemspec
@@ -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.
@@ -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.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -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 "" )