frappuccino 0.0.1 → 0.2.0

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.
@@ -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