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