frappuccino 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3aa6c347af81d9a074803f7be38c360db41adee4
4
+ data.tar.gz: 49da3f4765330f9ad73de449aa3080454f9f49e2
5
+ SHA512:
6
+ metadata.gz: 68c592a44ab4e948adfa4b8bb4214438cf3282cd9a3ce6047c436ad19471fa440b7d9703afc3fd0d9da386cc2873cc9091b7d80abdc8436973df00d0a79b4c40
7
+ data.tar.gz: cf1dc7f6d0394e3587714d668ab2a9aa304089ab4c5c239b7bbb8f406818ead40e6aa35dcc6c81c07ee236294090911018531eb3f96aa4484b5a0d902f420a04
@@ -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
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - rbx-nightly-19mode
6
+ - jruby-19mode
7
+ allow_failures:
8
+ - rvm: rbx-nightly-19mode
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frappuccino.gemspec
4
+ gemspec
5
+
6
+ gem 'coveralls', require: false
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Steve Klabnik
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,104 @@
1
+ # Frappuccino
2
+
3
+ Functional Reactive Programming for Ruby.
4
+
5
+ [![Build Status](https://travis-ci.org/steveklabnik/frappuccino.png?branch=master)](https://travis-ci.org/steveklabnik/frappuccino) [![Code Climate](https://codeclimate.com/github/steveklabnik/frappuccino.png)](https://codeclimate.com/github/steveklabnik/frappuccino) [![Coverage Status](https://coveralls.io/repos/steveklabnik/frappuccino/badge.png?branch=master)](https://coveralls.io/r/steveklabnik/frappuccino)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'frappuccino', github: "steveklabnik/frappuccino"
13
+ ```
14
+
15
+ (I'm hoping that @yoka will give me the gem name, until then, you
16
+ must install from GitHub.)
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ git clone https://github.com/steveklabnik/frappuccino
25
+ $ cd frappuccino
26
+ $ bundle
27
+ $ rake install
28
+
29
+ ## Usage
30
+
31
+ Basically, this:
32
+
33
+ ```ruby
34
+ require 'frappuccino'
35
+
36
+ class Button
37
+ def push
38
+ emit(:pushed) # emit sends a value into the stream
39
+ end
40
+ end
41
+
42
+ button = Button.new
43
+ stream = Frappuccino::Stream.new(button)
44
+
45
+ counter = stream
46
+ .map {|event| event == :pushed ? 1 : 0 } # convert events to ints
47
+ .inject(0) {|sum, n| sum + n } # add them up
48
+
49
+ counter.now # => 0
50
+
51
+ button.push
52
+ button.push
53
+ button.push
54
+
55
+ counter.now # => 3
56
+
57
+ button.push
58
+
59
+ counter.now # => 4
60
+ ```
61
+
62
+ You can also map via a hash, if you prefer:
63
+
64
+ ```ruby
65
+ .map(:pushed => 1, :default => 0)
66
+ ```
67
+
68
+ You can also register callbacks to a Stream. These will executed for
69
+ each event that occurs in the Stream:
70
+
71
+ ```ruby
72
+ stream.on_value do |event|
73
+ puts "I got a #{event}!"
74
+ end
75
+ ```
76
+
77
+ You can combine two streams together:
78
+
79
+ ```ruby
80
+ merged_stream = Frappuccino::Stream.merge(stream_one, stream_two)
81
+
82
+ # or
83
+
84
+ merged_stream = Frappuccino::Stream.new(button_one, button_two)
85
+ ```
86
+
87
+ You can select events from a stream, too:
88
+
89
+ ```ruby
90
+ stream = Frappuccino::Stream.new(button, something_else)
91
+ filtered_stream = stream.select{|event| event == :pushed }
92
+
93
+ filtered_stream.on_value do |event|
94
+ # event will only ever be :pushed
95
+ end
96
+ ```
97
+
98
+ ## Contributing
99
+
100
+ 1. Fork it
101
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
102
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 4. Push to the branch (`git push origin my-new-feature`)
104
+ 5. Create new Pull Request
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ t.ruby_opts = ["-w"]
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'frappuccino/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "frappuccino"
8
+ spec.version = Frappuccino::VERSION
9
+ spec.authors = ["Steve Klabnik"]
10
+ spec.email = ["steve@steveklabnik.com"]
11
+ spec.description = %q{A library to do Functional Reactive Programming in Ruby.}
12
+ spec.summary = %q{Functional Reactive Programming in Ruby.}
13
+ spec.homepage = "https://github.com/steveklabnik/frappuccino"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -1,140 +1,3 @@
1
- require "erb"
2
- require "fileutils"
3
- class Frappuccino
4
- attr_writer :files, :documentations, :doctree, :title
5
- def initialize title, files
6
- if File.directory? files
7
- docfiles = []
8
- Dir.glob("#{files}/*").each do |file|
9
- docfiles.push(file) unless File.directory? file
10
- end
11
- files = docfiles
12
- end
13
- @files = files
14
- @documentations = []
15
- @doctree = {:namespaces => {}}
16
- @title = title
17
- iterate_files
18
- documentations_to_doctree
19
- # sort alphabetically
20
- @doctree[:namespaces] = @doctree[:namespaces].sort_by {|k,v| k}
21
- generate_html
22
- end
23
-
24
- def iterate_files
25
- for file in @files
26
- File.open(file) do |opened_file|
27
- puts "* Parsing documentation from #{file}"
28
- collect_documentation opened_file
29
- end
30
- end
31
- end
32
-
33
- # parses documentation bodies
34
- def collect_documentation opened_file
35
- doc_start = false
36
- current_documentation = []
37
- opened_file.each_line do |line|
38
- # documentation starts here
39
- if line.include?("###") and !doc_start
40
- doc_start = true
41
- # documentation ends here
42
- elsif line.include?("###") and doc_start
43
- @documentations.push current_documentation
44
- current_documentation = []
45
- doc_start = false
46
- # in the middle of documentation
47
- elsif doc_start
48
- current_documentation.push line
49
- end
50
- end
51
- end
52
-
53
- def documentations_to_doctree
54
- @documentations.each do |docbody|
55
- doctree_format docbody
56
- end
57
- end
58
-
59
- # transforms array of strings to doctree hash
60
- def doctree_format docbody
61
- doctree = {}
62
- value_regexp = /^.*:\s(.*)/
63
- namespace_arr = []
64
- namespace_tree = {}
65
- function_name = nil
66
- object_name = nil
67
- description = ""
68
- rest_is_description = false
69
-
70
- docbody.each do |line|
71
- if line.match(/^\s*function/)
72
- function_name = line.match(value_regexp)[1]
73
- elsif line.match(/^\s*object/)
74
- object_name = line.match(value_regexp)[1]
75
- elsif line.match(/^\s*description/)
76
- rest_is_description = true
77
- elsif line.match(/^\s*namespace/)
78
- namespace_arr = line.match(value_regexp)[1].split(".")
79
- elsif rest_is_description
80
- description += line
81
- end
82
- end
83
-
84
- full_namespace = namespace_arr.join(".")
85
-
86
-
87
- if !function_name.nil?
88
- docdata = {:full_namespace => full_namespace, :functions => [{:function_name => function_name, :description => description}]}
89
- elsif !object_name.nil?
90
- docdata = {:full_namespace => full_namespace, :objects => [{:object_name => object_name, :description => description}]}
91
- else
92
- if rest_is_description
93
- docdata = {:full_namespace => full_namespace, :description => description}
94
- else
95
- docdata = {:full_namespace => full_namespace}
96
- end
97
- end
98
-
99
- namespace_is_added = false
100
- @doctree[:namespaces].each do |key, value|
101
- namespace_is_added = true if key == full_namespace
102
- end
103
-
104
- if namespace_is_added
105
- @doctree[:namespaces][full_namespace] = deep_safe_merge(@doctree[:namespaces][full_namespace], docdata)
106
- else
107
- @doctree[:namespaces][full_namespace] = docdata
108
- end
109
-
110
- end
111
-
112
-
113
- def generate_html
114
- template = ERB.new File.new("#{File.dirname(__FILE__)}/../template/index.erb").read
115
- rendered_template = template.result(binding)
116
- FileUtils.mkdir "docs" unless File.directory? "docs"
117
- File.open("docs/index.html", "w") do |f|
118
- f.write rendered_template
119
- end
120
- FileUtils.cp("#{File.dirname(__FILE__)}/../template/bg.png", "docs/bg.png")
121
- puts "* Documentation file generated (docs/index.html)"
122
- end
123
-
124
- # helpers:
125
-
126
- def deep_safe_merge(source_hash, new_hash)
127
- source_hash.merge(new_hash) do |key, old, new|
128
- if new.respond_to?(:blank) && new.blank?
129
- old
130
- elsif (old.kind_of?(Hash) and new.kind_of?(Hash))
131
- deep_merge(old, new)
132
- elsif (old.kind_of?(Array) and new.kind_of?(Array))
133
- old.concat(new).uniq
134
- else
135
- new
136
- end
137
- end
138
- end
139
-
140
- end
1
+ require "frappuccino/version"
2
+ require "frappuccino/stream"
3
+ require "frappuccino/property"
@@ -0,0 +1,16 @@
1
+ module Frappuccino
2
+ class Property
3
+ def initialize(zero, stream)
4
+ @value = zero
5
+ stream.add_observer(self)
6
+ end
7
+
8
+ def now
9
+ @value
10
+ end
11
+
12
+ def update(value)
13
+ @value = value
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ require 'observer'
2
+
3
+ module Frappuccino
4
+ module Source
5
+ def self.extended(object)
6
+ object.extend(Observable)
7
+ end
8
+
9
+ def emit(value)
10
+ changed
11
+ notify_observers(value)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+
3
+ # before we require all of the subclasses, we need to have Stream defined
4
+ module Frappuccino
5
+ class Stream
6
+ end
7
+ end
8
+
9
+ require 'frappuccino/source'
10
+
11
+ require 'frappuccino/stream/map'
12
+ require 'frappuccino/stream/select'
13
+ require 'frappuccino/stream/zip'
14
+ require 'frappuccino/stream/drop'
15
+ require 'frappuccino/stream/scan'
16
+ require 'frappuccino/stream/take'
17
+
18
+ def not_implemented(m, message)
19
+ define_method m do |*args, &blk|
20
+ raise NotImplementedError, "##{m} is not supported, because #{message}."
21
+ end
22
+ end
23
+
24
+ module Frappuccino
25
+ class Stream
26
+ include Observable
27
+
28
+ def initialize(*sources)
29
+ sources.each do |source|
30
+ source.extend(Frappuccino::Source).add_observer(self)
31
+ end
32
+ end
33
+
34
+ def update(event)
35
+ occur(event)
36
+ end
37
+
38
+ def count(*args, &blk)
39
+ stream = if args.count > 0
40
+ self.select { |value| value == args.first }
41
+ elsif blk
42
+ self.select { |value| blk.call(value) }
43
+ else
44
+ self
45
+ end
46
+
47
+ Property.new(0, stream.scan(0) { |last| last + 1 })
48
+ end
49
+
50
+ not_implemented(:cycle, "it relies on having a end to the Enumerable")
51
+ not_implemented(:all?, "it needs a stream that terminates.")
52
+ not_implemented(:chunk, "it needs a stream that terminates.")
53
+ not_implemented(:any?, "it could resolve to ⊥. You probably want #select")
54
+ not_implemented(:find, "it could resolve to ⊥. You probably want #select")
55
+
56
+ alias :detect :find
57
+
58
+ def map(hash = nil, &blk)
59
+ blk = lambda { |event| hash.fetch(event) { hash[:default] } } if hash
60
+ Map.new(self, &blk)
61
+ end
62
+ alias :collect :map
63
+ alias :map_stream :map
64
+
65
+ def drop(n)
66
+ Drop.new(self, n)
67
+ end
68
+
69
+ def take(n)
70
+ Take.new(self, n)
71
+ end
72
+
73
+ def inject(start, &blk)
74
+ Property.new(start, self.scan(start, &blk))
75
+ end
76
+
77
+ def select(&blk)
78
+ Select.new(self, &blk)
79
+ end
80
+
81
+ def zip(stream)
82
+ Zip.new(self, stream)
83
+ end
84
+
85
+ def scan(zero, &blk)
86
+ Scan.new(self, zero, &blk)
87
+ end
88
+
89
+ def on_value(&blk)
90
+ callbacks << blk
91
+ end
92
+
93
+ def self.merge(stream_one, stream_two)
94
+ new(stream_one, stream_two)
95
+ end
96
+
97
+ protected
98
+
99
+ def occur(value)
100
+ callbacks.each do |callback|
101
+ callback.call(value)
102
+ end
103
+
104
+ changed
105
+ notify_observers(value)
106
+ end
107
+
108
+ def callbacks
109
+ @callbacks ||= []
110
+ end
111
+ end
112
+ end