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