frypan 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb8a7b23f1048487bc5ab065783383c02ce47b1d
4
- data.tar.gz: 93c85039222d259ad54acab69dadb06e653811f6
3
+ metadata.gz: 041c764b352e067de7265cb532797f1b3b5d5b7a
4
+ data.tar.gz: d0f5212808ffb5d2eb2b6d7ea6d863c61c238e0d
5
5
  SHA512:
6
- metadata.gz: 7af33ae5164675458049fd5be4bc2c7ca67333ca1ca4dd6e525e6575074c905367d092383a5c4bdde9d53269494c76ff8335aa2f86215a86accc1f0152df47df
7
- data.tar.gz: c52cf2274ac18309bf4ecbc09164511dcba3ec2149fb5fd118713f1e66828d78631be153de21a29516920cb1bd1a3d4857e23b581b34bc2b4fbb542fa88adc1a
6
+ metadata.gz: 9e66378e9eb9f02eec905f668abe9da03271f13d09a6899c0ff6a8b039775dd39498d089f994bb2450bec754b85f56ce828d8e6f1e43d381b88cb11065c7b2e6
7
+ data.tar.gz: 7b1df425490c8575c91498ae9da32fe7d03232b6a7c915d669020431619bc4173140a88cd4514f131d28ab25602bdc46c618cff2e50e3fb556f4cbcf78d3d3d4
data/.gitignore CHANGED
@@ -11,6 +11,6 @@
11
11
  *.so
12
12
  *.o
13
13
  *.a
14
+ mkmf.log
14
15
  *~
15
16
  *#
16
- mkmf.log
@@ -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
@@ -1,3 +1,5 @@
1
+ # -*- mode:ruby -*-
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rake/testtask"
3
5
 
@@ -1,4 +1,4 @@
1
- # -*- mode: ruby -*-
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{Yet another recording scheduler.}
13
- spec.description = %q{}
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")
@@ -1,57 +1,196 @@
1
1
  require "frypan/version"
2
2
 
3
3
  module Frypan
4
- # TODO
5
- # imp APIServer
6
- # imp EpgParser
7
-
8
- def self.main_signal(db, tuner_config=nil)
9
- dbsync_ui_res = DBSyncList.new("ui_response", db)
10
- dbsync_recording_log = DBSyncList.new("recording_log", db)
11
- dbsync_program = DBSyncList.new("program", db)
12
- dbsync_recorded = DBSyncList.new("recorded", db)
13
-
14
- dbsync_reserve1 = DBSyncList.new("reserve1", db)
15
- dbsync_reserve2 = DBSyncList.new("reserve2", db)
16
- dbsync_reserve3 = DBSyncList.new("reserve3", db)
17
- dbsync_reserve4 = DBSyncList.new("reserve4", db)
18
-
19
- api_server = APIServer.new(db) # fork webrick. only op-command throw.
20
-
21
- command = TinyFRP.lift{ api_server.get_next_command } # one-unit-command OR nil
22
- #com = {
23
- # command_id: 1024,
24
- # target_tuner: 1
25
- # operations: [
26
- # {:kind => :remove, ::target => {reserve data (something including start_time)}},
27
- # {:kind => :add, :target => {reserve data}}
28
- # ]
29
- #}
30
-
31
- current_time = TinyFRP.lift{ Time.now.strftime("%Y%m%d%H%M%S") }
32
-
33
- s1 = TinyFRP.bundle(command, current_time) >> Node.ReserveListFoldp("tuner1", dbsync_reserve1.to_a)
34
- s2 = TinyFRP.bundle(command, current_time) >> Node.ReserveListFoldp("tuner2", dbsync_reserve2.to_a)
35
- s3 = TinyFRP.bundle(command, current_time) >> Node.ReserveListFoldp("tuner3", dbsync_reserve3.to_a)
36
- s4 = TinyFRP.bundle(command, current_time) >> Node.ReserveListFoldp("tuner4", dbsync_reserve4.to_a)
37
-
38
- ui_res = Node.UIresponseFoldp(dbsync_ui_res) << TinyFRP.bundle(s1, s2, s3, s4)
39
-
40
- ps1 = s1 >> Node.PersistantReserveListFoldp("tuner1", dbsync_reserve1)
41
- ps2 = s2 >> Node.PersistantReserveListFoldp("tuner2", dbsync_reserve2)
42
- ps3 = s3 >> Node.PersistantReserveListFoldp("tuner3", dbsync_reserve3)
43
- ps4 = s4 >> Node.PersistantReserveListFoldp("tuner4", dbsync_reserve4)
44
-
45
- t1 = s1 >> Node.RecordingAsyncFoldp(Tuner.new("fmt1"))
46
- t2 = s2 >> Node.RecordingAsyncFoldp(Tuner.new("fmt2"))
47
- t3 = s3 >> Node.RecordingAsyncFoldp(Tuner.new("fmt2"))
48
- t4 = s4 >> Node.RecordingAsyncFoldp(Tuner.new("fmt2"))
49
-
50
- recording_log = TinyFRP.bundle(t1, t2, t3, t4) >> Node.RecordingLogFoldp(dbsync_recording_log)
51
- epg_extract = recording_log >> Node.EpgExtractAsyncFoldp(EpgParser.new)
52
- program_list = epg_extract >> Node.ProgramListFoldp(dbsync_program)
53
- recorded_list = recording_log >> Node.RecordedListFoldp(dbsync_recorded)
54
-
55
- return TinyFRP.bundle(ui_res, ps1, ps2, ps3, ps4, program_list, recorded_list) >> TinyFRP.bottom
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
@@ -1,3 +1,3 @@
1
1
  module Frypan
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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
@@ -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
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+
5
+ socket = TCPSocket.open("localhost", 20000)
6
+ socket.write(STDIN.gets)
7
+ puts socket.gets
8
+ socket.close