frypan 0.0.1 → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/README.md +33 -0
- data/Rakefile +2 -0
- data/frypan.gemspec +3 -4
- data/lib/frypan.rb +191 -52
- data/lib/frypan/version.rb +1 -1
- data/tutorial/my_at.rb +17 -0
- data/tutorial/my_atd.rb +85 -0
- data/tutorial/sample_tcp_client.rb +8 -0
- data/tutorial/sample_tcp_server.rb +13 -0
- data/tutorial/tutorial.md +359 -0
- data/unit_test/test_frypan.rb +66 -0
- metadata +14 -14
- data/_README.md +0 -31
- data/bin/frypan +0 -55
- data/lib/frypan/database_sync_list.rb +0 -30
- data/lib/frypan/epg_parser.rb +0 -23
- data/lib/frypan/node.rb +0 -150
- data/lib/frypan/tuner.rb +0 -47
- data/unit_test/test_node.rb +0 -171
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 041c764b352e067de7265cb532797f1b3b5d5b7a
|
4
|
+
data.tar.gz: d0f5212808ffb5d2eb2b6d7ea6d863c61c238e0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e66378e9eb9f02eec905f668abe9da03271f13d09a6899c0ff6a8b039775dd39498d089f994bb2450bec754b85f56ce828d8e6f1e43d381b88cb11065c7b2e6
|
7
|
+
data.tar.gz: 7b1df425490c8575c91498ae9da32fe7d03232b6a7c915d669020431619bc4173140a88cd4514f131d28ab25602bdc46c618cff2e50e3fb556f4cbcf78d3d3d4
|
data/.gitignore
CHANGED
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Frypan
|
2
|
+
|
3
|
+
Very small and simple library to do Functional Reactive Programming in Ruby in a similar way to Elm.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'frypan'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install frypan
|
20
|
+
|
21
|
+
## Short Tutorial
|
22
|
+
|
23
|
+
To experience FRP and understand how to use this library, we make an imitation of Linux's atd(8) and at(1).
|
24
|
+
|
25
|
+
[Click here to jump to Short Tutorial page.](https://github.com/sawaken/frypan/blob/master/tutorial/tutorial.md "Frypan - Short Tutorial")
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
1. Fork it ( https://github.com/sawaken/frypan/fork )
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create a new Pull Request
|
data/Rakefile
CHANGED
data/frypan.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
#-*- mode:ruby -*-
|
2
2
|
# coding: utf-8
|
3
3
|
lib = File.expand_path('../lib', __FILE__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
@@ -9,9 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.version = Frypan::VERSION
|
10
10
|
spec.authors = ["sawaken"]
|
11
11
|
spec.email = ["sasasawada@gmail.com"]
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.
|
14
|
-
spec.homepage = ""
|
12
|
+
spec.summary = %q{Very small and simple library to do Functional Reactive Programming in Ruby in a similar way to Elm.}
|
13
|
+
spec.homepage = "https://github.com/sawaken/tiny_frp2"
|
15
14
|
spec.license = "MIT"
|
16
15
|
|
17
16
|
spec.files = `git ls-files -z`.split("\x0")
|
data/lib/frypan.rb
CHANGED
@@ -1,57 +1,196 @@
|
|
1
1
|
require "frypan/version"
|
2
2
|
|
3
3
|
module Frypan
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
4
|
+
|
5
|
+
# Reactor : Object to calculate (Signal -> Value)
|
6
|
+
# ----------------------------------------
|
7
|
+
|
8
|
+
class Reactor
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def initialize(signal, init_memos=[{}, {}])
|
12
|
+
@signal = signal
|
13
|
+
@init_memos = init_memos
|
14
|
+
end
|
15
|
+
|
16
|
+
def loop(&block)
|
17
|
+
memos = @init_memos
|
18
|
+
while true
|
19
|
+
last_memo = @signal.__pull({}, *memos)
|
20
|
+
memos = [last_memo, memos[0]]
|
21
|
+
block.call(last_memo[@signal])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
loop(&block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Signal : Abstraction of time-varing value
|
31
|
+
# ----------------------------------------
|
32
|
+
|
33
|
+
class Signal
|
34
|
+
|
35
|
+
# Utility Class methods (public to library-user)
|
36
|
+
# ----------------------------------------
|
37
|
+
|
38
|
+
def self.const(val)
|
39
|
+
Const.new(val)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.input(&proc)
|
43
|
+
Input.new(&proc)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.async_input(buf_size=1, &proc)
|
47
|
+
InputThread.new(buf_size, &proc)[0]
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.lift(*arg_signals, &proc)
|
51
|
+
Lift.new(*arg_signals, &proc)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.foldp(init_state, *arg_signals, &proc)
|
55
|
+
Foldp.new(init_state, *arg_signals, &proc)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Utility Instance methods (public to library-user)
|
59
|
+
# ----------------------------------------
|
60
|
+
|
61
|
+
def method_missing(name, *args, &proc)
|
62
|
+
Lift.new(self){|a| a.send(name, *args, &proc)}
|
63
|
+
end
|
64
|
+
|
65
|
+
def lift(&proc)
|
66
|
+
Lift.new(self, &proc)
|
67
|
+
end
|
68
|
+
|
69
|
+
def foldp(init_state, &proc)
|
70
|
+
Foldp.new(init_state, self, &proc)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Implementation of each Signals (library-user need not understand)
|
74
|
+
# ----------------------------------------
|
75
|
+
|
76
|
+
def __pull(memo0, memo1, memo2)
|
77
|
+
if memo0.has_key?(self)
|
78
|
+
memo0
|
79
|
+
else
|
80
|
+
__calc(__pull_deps(memo0, memo1, memo2), memo1, memo2)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def __pull_deps(memo0, memo1, memo2)
|
85
|
+
memo0
|
86
|
+
end
|
87
|
+
|
88
|
+
def __same(memo_a, memo_b)
|
89
|
+
memo_a.has_key?(self) && memo_b.has_key?(self) && memo_a[self] == memo_b[self]
|
90
|
+
end
|
91
|
+
|
92
|
+
class Const < Signal
|
93
|
+
def initialize(val)
|
94
|
+
@val = val
|
95
|
+
end
|
96
|
+
|
97
|
+
def __calc(memo0, memo1, memo2)
|
98
|
+
memo0.merge(self => @val)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Input < Signal
|
103
|
+
def initialize(&proc)
|
104
|
+
@input_proc = proc
|
105
|
+
end
|
106
|
+
|
107
|
+
def __calc(memo0, memo1, memo2)
|
108
|
+
memo0.merge(self => @input_proc.call)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class InputThread < Signal
|
113
|
+
def initialize(buf_size=1, &proc)
|
114
|
+
@buf_size = buf_size
|
115
|
+
@proc = proc
|
116
|
+
end
|
117
|
+
|
118
|
+
def atom(thread, &block)
|
119
|
+
thread[:mutex].synchronize(&block)
|
120
|
+
end
|
121
|
+
|
122
|
+
def make_thread
|
123
|
+
thread = ::Thread.new(@proc) do |proc|
|
124
|
+
sleep
|
125
|
+
while true
|
126
|
+
input = proc.call
|
127
|
+
size = atom(Thread.current){
|
128
|
+
Thread.current[:inputs] << input
|
129
|
+
Thread.current[:inputs].size
|
130
|
+
}
|
131
|
+
sleep if size >= @buf_size
|
132
|
+
end
|
133
|
+
end
|
134
|
+
thread[:mutex] = Mutex.new
|
135
|
+
thread[:inputs] = []
|
136
|
+
thread.run
|
137
|
+
return thread
|
138
|
+
end
|
139
|
+
|
140
|
+
def get_inputs(thread)
|
141
|
+
atom(thread){
|
142
|
+
inputs = thread[:inputs]
|
143
|
+
thread[:inputs] = []
|
144
|
+
return inputs
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def __calc(memo0, memo1, memo2)
|
149
|
+
unless memo1.has_key?(self)
|
150
|
+
memo0.merge(self => [[], make_thread])
|
151
|
+
else
|
152
|
+
inputs = get_inputs(memo1[self][1])
|
153
|
+
memo1[self][1].run
|
154
|
+
return memo0.merge(self => [inputs, memo1[self][1]])
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class Lift < Signal
|
160
|
+
def initialize(*arg_signals, &proc)
|
161
|
+
@arg_signals, @proc = arg_signals, proc
|
162
|
+
end
|
163
|
+
|
164
|
+
def __pull_deps(memo0, memo1, memo2)
|
165
|
+
@arg_signals.inject(memo0){|acc, sig| sig.__pull(acc, memo1, memo2)}
|
166
|
+
end
|
167
|
+
|
168
|
+
def __calc(memo0, memo1, memo2)
|
169
|
+
if @arg_signals.all?{|sig| sig.__same(memo0, memo1)}
|
170
|
+
memo0.merge(self => memo1[self])
|
171
|
+
else
|
172
|
+
memo0.merge(self => @proc.call(*@arg_signals.map{|sig| memo0[sig]}))
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class Foldp < Signal
|
178
|
+
def initialize(init_state, *arg_signals, &proc)
|
179
|
+
@init_state, @arg_signals, @proc = init_state, arg_signals, proc
|
180
|
+
end
|
181
|
+
|
182
|
+
def __pull_deps(memo0, memo1, memo2)
|
183
|
+
@arg_signals.inject(memo0){|acc, sig| sig.__pull(acc, memo1, memo2)}
|
184
|
+
end
|
185
|
+
|
186
|
+
def __calc(memo0, memo1, memo2)
|
187
|
+
if __same(memo1, memo2) && @arg_signals.all?{|sig| sig.__same(memo0, memo1)}
|
188
|
+
memo0.merge(self => memo1[self])
|
189
|
+
else
|
190
|
+
state = memo1.has_key?(self) ? memo1[self] : @init_state
|
191
|
+
memo0.merge(self => @proc.call(state, *@arg_signals.map{|sig| memo0[sig]}))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
56
195
|
end
|
57
196
|
end
|
data/lib/frypan/version.rb
CHANGED
data/tutorial/my_at.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
loop do
|
6
|
+
print "execution-command> "
|
7
|
+
com = STDIN.gets
|
8
|
+
print "execution-time> "
|
9
|
+
time = STDIN.gets
|
10
|
+
|
11
|
+
break unless com && time
|
12
|
+
|
13
|
+
socket = TCPSocket.open("localhost", 20000)
|
14
|
+
socket.write(com + time)
|
15
|
+
puts "response: #{socket.gets}"
|
16
|
+
socket.close
|
17
|
+
end
|
data/tutorial/my_atd.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'frypan'
|
4
|
+
require 'socket'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
S = Frypan::Signal
|
8
|
+
|
9
|
+
socket = TCPServer.new(20000)
|
10
|
+
puts "my_atd listening port: 20000..."
|
11
|
+
|
12
|
+
accepter = proc do
|
13
|
+
connection = socket.accept
|
14
|
+
c, t = connection.gets, connection.gets
|
15
|
+
{command_str: c.chomp, time_str: t.chomp, connection: connection}
|
16
|
+
end
|
17
|
+
|
18
|
+
timer = proc do
|
19
|
+
Time.now
|
20
|
+
end
|
21
|
+
|
22
|
+
update_commands = proc do |commands, ct, cs|
|
23
|
+
{
|
24
|
+
waitings: commands[:waitings].select{|c| c[:time] > ct} +
|
25
|
+
cs.select{|c| acceptable?(c)}.map{|c| c.update(time: Time.parse(c[:time_str]))},
|
26
|
+
launcheds: commands[:waitings].select{|c| c[:time] <= ct},
|
27
|
+
rejecteds: cs.reject{|c| acceptable?(c)}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
output_formatize = proc do |coms, cs|
|
32
|
+
ress = cs.map do |c|
|
33
|
+
if coms[:rejecteds].include?(c)
|
34
|
+
msg = "Your specified time '#{c[:time_str]}' seems invalid.\n"
|
35
|
+
else
|
36
|
+
msg = "OK.\n"
|
37
|
+
end
|
38
|
+
{message: msg, connection: c[:connection]}
|
39
|
+
end
|
40
|
+
{commands: coms[:launcheds].map{|c| c[:command_str]}, responses: ress}
|
41
|
+
end
|
42
|
+
|
43
|
+
exec_commands = proc do |commands|
|
44
|
+
commands.each do |command|
|
45
|
+
puts "execute command '#{command}'"
|
46
|
+
pid = Process.spawn(command, :out => STDOUT, :err => STDERR)
|
47
|
+
puts "PID = #{pid}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
respond = proc do |responses|
|
52
|
+
responses.each do |res|
|
53
|
+
res[:connection].write(res[:message])
|
54
|
+
res[:connection].close
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
output_processor = proc do |out|
|
59
|
+
exec_commands.call(out[:commands])
|
60
|
+
respond.call(out[:responses])
|
61
|
+
end
|
62
|
+
|
63
|
+
# helper method (pure function)
|
64
|
+
def acceptable?(c)
|
65
|
+
Time.parse(c[:time_str])
|
66
|
+
rescue
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Input-Signal representing new clients.
|
71
|
+
new_clients = S.async_input(5, &accepter)
|
72
|
+
|
73
|
+
# Input-Signal representing current time.
|
74
|
+
current_time = S.input(&timer)
|
75
|
+
|
76
|
+
# Foldp-Signal representing registered commands.
|
77
|
+
initial = {waitings: [], launcheds: [], rejecteds: []}
|
78
|
+
commands = S.foldp(initial, current_time, new_clients, &update_commands)
|
79
|
+
|
80
|
+
# Lift-Signal representing output datas.
|
81
|
+
main = S.lift(commands, new_clients, &output_formatize)
|
82
|
+
|
83
|
+
# FRP's main-loop.
|
84
|
+
Frypan::Reactor.new(main).loop(&output_processor)
|
85
|
+
|