smcty 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/smcty +4 -0
- data/lib/smcty.rb +1 -0
- data/lib/smcty/allocation.rb +26 -0
- data/lib/smcty/configuration.rb +61 -0
- data/lib/smcty/configurator.rb +178 -0
- data/lib/smcty/console.rb +229 -0
- data/lib/smcty/factory.rb +105 -0
- data/lib/smcty/helpers.rb +16 -0
- data/lib/smcty/job.rb +72 -0
- data/lib/smcty/output.rb +71 -0
- data/lib/smcty/planner.rb +13 -0
- data/lib/smcty/production.rb +31 -0
- data/lib/smcty/project.rb +23 -0
- data/lib/smcty/resource.rb +46 -0
- data/lib/smcty/scheduling.rb +198 -0
- data/lib/smcty/store.rb +115 -0
- data/lib/smcty/version.rb +3 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 066404cac54ab6ea59d05f19655509a73fe25879
|
4
|
+
data.tar.gz: 9fb49f6ed6a8d580f4f63d8558ce012b5ebc1fab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79c2493d00915a80980b58991bd7f13a38fdb5f0fd5ebb79884c29f09cbcb584d359824b01085a5a7488a1a458c5bd5d27659f130cae8cda89751e0d13a9e969
|
7
|
+
data.tar.gz: dde05154fcd122b9f13c0907716bfb8f0cebfdcad6b4a8756bcdf8238ab3407deb760266b556bcbb20eabb848fa1504b4bc075ad38ac8c294c05a011abd7e5a3
|
data/bin/smcty
ADDED
data/lib/smcty.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'smcty/planner'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Allocation
|
3
|
+
attr_reader :store, :resource, :amount
|
4
|
+
|
5
|
+
def initialize(store, resource, amount)
|
6
|
+
@store = store
|
7
|
+
@resource = resource
|
8
|
+
@amount = amount
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
@store.valid?(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get
|
16
|
+
@store.get(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{
|
21
|
+
"resource" => @resource.name,
|
22
|
+
"amount" => @amount
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Configuration
|
3
|
+
attr_reader :created_at, :store, :scheduler
|
4
|
+
|
5
|
+
def initialize(store)
|
6
|
+
@created_at = Time.new
|
7
|
+
@store = store
|
8
|
+
@factories = {}
|
9
|
+
@scheduler = Scheduling.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def register_factory(factory)
|
13
|
+
@factories[factory.name] = factory
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource_by_name(resource_name)
|
17
|
+
@factories.values.each do |factory|
|
18
|
+
resource = factory.resource_by_name(resource_name)
|
19
|
+
return resource if resource
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def resources
|
25
|
+
result = []
|
26
|
+
@factories.values.each do |f|
|
27
|
+
result += f.resources
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
def factory_for(resource)
|
33
|
+
@factories.values.each do |factory|
|
34
|
+
found = factory.resource_by_name(resource.name)
|
35
|
+
return factory if found
|
36
|
+
end
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def factories
|
41
|
+
@factories.keys.sort
|
42
|
+
end
|
43
|
+
|
44
|
+
def factory(name)
|
45
|
+
@factories[name]
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"Configuration created at #{@created_at}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_hash
|
53
|
+
{
|
54
|
+
"store" => @store.to_hash,
|
55
|
+
"factories" => @factories.values.map{|f| f.to_hash},
|
56
|
+
"scheduling" => @scheduler.to_hash
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'smcty/store'
|
4
|
+
require 'smcty/configuration'
|
5
|
+
require 'smcty/factory'
|
6
|
+
require 'smcty/resource'
|
7
|
+
|
8
|
+
module Smcty
|
9
|
+
class Configurator
|
10
|
+
attr_reader :configuration
|
11
|
+
|
12
|
+
def initialize(config_path)
|
13
|
+
@config_path = config_path
|
14
|
+
@configuration = Configurator.read_configuration(config_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def save
|
18
|
+
Configurator.write_configuration(@configuration, @config_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.read_configuration(path)
|
22
|
+
file = File.read(path)
|
23
|
+
data_hash = JSON.parse(file)
|
24
|
+
|
25
|
+
if (data_hash && data_hash != {})
|
26
|
+
store_hash = read_value(data_hash, "store", true)
|
27
|
+
store_name = read_value(store_hash, "name", true)
|
28
|
+
store_capacity = read_value(store_hash, "capacity", true)
|
29
|
+
store = Store.new(store_name, store_capacity)
|
30
|
+
|
31
|
+
configuration = Configuration.new(store)
|
32
|
+
|
33
|
+
deferred_dependencies = []
|
34
|
+
|
35
|
+
factories_list = read_value(data_hash, "factories", true)
|
36
|
+
factories_list.each do |factory_hash|
|
37
|
+
factory_name = read_value(factory_hash, "name", true)
|
38
|
+
factory_capacity = read_value(factory_hash, "capacity", true)
|
39
|
+
factory_sequential = read_value(factory_hash, "sequential", false, false)
|
40
|
+
|
41
|
+
factory = Factory.new(factory_name, factory_capacity, factory_sequential)
|
42
|
+
configuration.register_factory(factory)
|
43
|
+
|
44
|
+
factory_resources = read_value(factory_hash, "resources", true)
|
45
|
+
factory_resources.each do |resources_hash|
|
46
|
+
resource_name = read_value(resources_hash, "name", true)
|
47
|
+
resource_description = read_value(resources_hash, "description", true)
|
48
|
+
resource_time = read_value(resources_hash, "time", true)
|
49
|
+
|
50
|
+
resource = Resource.new(resource_name, resource_description)
|
51
|
+
factory.register_resource(resource, resource_time)
|
52
|
+
|
53
|
+
resource_deps = read_value(resources_hash, "deps", false, [])
|
54
|
+
resource_deps.each do |dependency_hash|
|
55
|
+
dependency_name = read_value(dependency_hash, "name", true)
|
56
|
+
dependency_amount = read_value(dependency_hash, "amount", true)
|
57
|
+
|
58
|
+
if dep_resource = configuration.resource_by_name(dependency_name)
|
59
|
+
resource.register_dependency(dep_resource, dependency_amount)
|
60
|
+
else
|
61
|
+
deferred_dependencies << {resource: resource,
|
62
|
+
dependency_name: dependency_name,
|
63
|
+
dependency_amount: dependency_amount
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# post process deferred dependencies
|
71
|
+
deferred_dependencies.each do |deferred_dep|
|
72
|
+
resource = deferred_dep[:resource]
|
73
|
+
dependency_name = deferred_dep[:dependency_name]
|
74
|
+
amount = deferred_dep[:dependency_amount]
|
75
|
+
|
76
|
+
dependent_resource = configuration.resource_by_name(dependency_name)
|
77
|
+
unless dependent_resource
|
78
|
+
raise "Cannot fullfill dependency from #{resource.name} -> #{dependency_name}"
|
79
|
+
end
|
80
|
+
|
81
|
+
resource.register_dependency(dependent_resource, amount)
|
82
|
+
end
|
83
|
+
|
84
|
+
# post process stock of store
|
85
|
+
read_value(store_hash, "stock", false, []).each do |stock_hash|
|
86
|
+
resource_name = read_value(stock_hash, "name", true)
|
87
|
+
resource_amount = read_value(stock_hash, "amount", true)
|
88
|
+
|
89
|
+
resource = configuration.resource_by_name(resource_name) || Resource.new(resource_name, "unmanaged")
|
90
|
+
store.put(resource, resource_amount)
|
91
|
+
end
|
92
|
+
|
93
|
+
# post process the scheduling information
|
94
|
+
scheduling_hash = read_value(data_hash, "scheduling", true)
|
95
|
+
read_value(scheduling_hash, "projects", false, []).each do |project_hash|
|
96
|
+
project_name = read_value(project_hash, "name", true)
|
97
|
+
project = Project.new(project_name)
|
98
|
+
read_value(project_hash, "requirements", true).each do |req|
|
99
|
+
resource_name = read_value(req, "resource", true)
|
100
|
+
amount = read_value(req, "amount", true).to_i
|
101
|
+
resource = configuration.resource_by_name(resource_name)
|
102
|
+
unless resource
|
103
|
+
raise "Cannot register requirement for unknown resource #{resource_name}"
|
104
|
+
end
|
105
|
+
project.add_requirement(resource, amount)
|
106
|
+
end
|
107
|
+
|
108
|
+
job_dict = {}
|
109
|
+
job_dependencies = {}
|
110
|
+
jobs = []
|
111
|
+
|
112
|
+
# first pass to create the single jobs
|
113
|
+
read_value(project_hash, "jobs", true).each do |job_hash|
|
114
|
+
reference = read_value(job_hash, "id", true)
|
115
|
+
resource_name = read_value(job_hash, "resource", true)
|
116
|
+
resource = configuration.resource_by_name(resource_name)
|
117
|
+
unless resource
|
118
|
+
raise "Cannot create job for unknown resource #{resource_name}"
|
119
|
+
end
|
120
|
+
job = Job.new(resource)
|
121
|
+
job_dict[reference] = job
|
122
|
+
jobs << job
|
123
|
+
|
124
|
+
allocation_hash = read_value(job_hash, "allocation", false, nil)
|
125
|
+
if allocation_hash
|
126
|
+
amount = read_value(allocation_hash, "amount", true).to_i
|
127
|
+
allocation = store.allocate(resource, amount)
|
128
|
+
job.allocate(allocation) if allocation
|
129
|
+
end
|
130
|
+
|
131
|
+
production_hash = read_value(job_hash, "production", false, nil)
|
132
|
+
if production_hash
|
133
|
+
start_time = read_value(production_hash, "start_time", true).to_i
|
134
|
+
duration = read_value(production_hash, "duration", true).to_i
|
135
|
+
production = Production.new(resource, duration, start_time)
|
136
|
+
job.produce(production)
|
137
|
+
end
|
138
|
+
|
139
|
+
dependency_list = read_value(job_hash, "dependent_jobs", false, nil)
|
140
|
+
job_dependencies[job] = dependency_list if dependency_list
|
141
|
+
end
|
142
|
+
|
143
|
+
# second pass to connect dependent jobs
|
144
|
+
job_dependencies.keys.each do |job|
|
145
|
+
job_dependencies[job].each do |dep_ref|
|
146
|
+
dep_job = job_dict[dep_ref]
|
147
|
+
unless dep_job
|
148
|
+
raise "job references another job which is not known (#{dep_ref})"
|
149
|
+
end
|
150
|
+
job.add_dependent(dep_job)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
configuration.scheduler.load_project(project, jobs)
|
155
|
+
end
|
156
|
+
|
157
|
+
configuration
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.write_configuration(configuration, path)
|
162
|
+
File.open(path,"w") do |f|
|
163
|
+
f.write(configuration.to_hash.to_json)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def self.read_value(hash, key, force, default=nil)
|
170
|
+
value = hash[key]
|
171
|
+
if force && (value == nil)
|
172
|
+
raise "The hash #{hash} does not contain the key #{key}"
|
173
|
+
end
|
174
|
+
value || default
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'smcty/configurator'
|
2
|
+
require 'smcty/output'
|
3
|
+
require 'smcty/helpers'
|
4
|
+
require 'smcty/production'
|
5
|
+
require 'smcty/project'
|
6
|
+
require 'smcty/scheduling'
|
7
|
+
|
8
|
+
module Smcty
|
9
|
+
class Console
|
10
|
+
|
11
|
+
# Initialize the console with the input stream (e.g. $stdin)
|
12
|
+
# and a path to smcty configuration file.
|
13
|
+
def initialize(input_stream, config_path)
|
14
|
+
@in = input_stream
|
15
|
+
@configurator = Configurator.new(config_path)
|
16
|
+
@configuration = @configurator.configuration
|
17
|
+
@productions = {}
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def prompt!
|
22
|
+
run = true
|
23
|
+
while run
|
24
|
+
"-> ".display
|
25
|
+
run = dispatch(@in.gets.chomp)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Commands
|
30
|
+
#
|
31
|
+
# quit
|
32
|
+
#
|
33
|
+
# store -> list the inventory of the store
|
34
|
+
# store put #item #amount
|
35
|
+
#
|
36
|
+
# factory -> list the registered factories
|
37
|
+
# factory #name -> list the resources and the production of the named factory
|
38
|
+
#
|
39
|
+
# resources -> list all managed resources
|
40
|
+
#
|
41
|
+
# project add #label [#resource:#amount]+
|
42
|
+
# project #label
|
43
|
+
#
|
44
|
+
# produce #resource
|
45
|
+
#
|
46
|
+
# pick #production-number
|
47
|
+
#
|
48
|
+
# next
|
49
|
+
#
|
50
|
+
def dispatch(input)
|
51
|
+
continue = true
|
52
|
+
commands = input.split(" ")
|
53
|
+
case commands[0].downcase
|
54
|
+
when "store"
|
55
|
+
continue = process_store(commands[1..-1])
|
56
|
+
when "factory"
|
57
|
+
continue = process_factory(commands[1..-1])
|
58
|
+
when "quit"
|
59
|
+
continue = process_quit
|
60
|
+
when "resources"
|
61
|
+
continue = process_resources
|
62
|
+
when "project"
|
63
|
+
continue = process_projects(commands[1..-1])
|
64
|
+
when "produce"
|
65
|
+
continue = process_production(commands[1..-1])
|
66
|
+
when "pick"
|
67
|
+
continue = process_pick(commands[1..-1])
|
68
|
+
when "next"
|
69
|
+
continue = process_next
|
70
|
+
when "save"
|
71
|
+
continue = process_save
|
72
|
+
when "scheduling"
|
73
|
+
continue = process_scheduling
|
74
|
+
else
|
75
|
+
puts "unknown command: #{input}"
|
76
|
+
end
|
77
|
+
return continue
|
78
|
+
end
|
79
|
+
|
80
|
+
def process_scheduling
|
81
|
+
@configuration.scheduler.print_job_lists
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def process_next
|
86
|
+
puts "DO > #{@configuration.scheduler.next}"
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def process_projects(commands)
|
91
|
+
if commands.size == 1
|
92
|
+
puts "print the project"
|
93
|
+
elsif commands.size > 2
|
94
|
+
unless commands[0] == "add"
|
95
|
+
puts "unknow command #{commands[0]} for project is not known"
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
project = Project.new(commands[1])
|
99
|
+
requirements = commands[2..-1]
|
100
|
+
requirements.each do |r|
|
101
|
+
items = r.split(":")
|
102
|
+
r_name = items[0]
|
103
|
+
amount = items[1].to_i
|
104
|
+
resource = @configuration.resource_by_name(r_name)
|
105
|
+
unless resource
|
106
|
+
puts "the resource #{resource_name} is not known"
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
project.add_requirement(resource, amount)
|
110
|
+
end
|
111
|
+
@configuration.scheduler.plan_project(project)
|
112
|
+
puts "planned the project"
|
113
|
+
else
|
114
|
+
puts "not enough parameters for project command"
|
115
|
+
end
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
def process_pick(commands)
|
120
|
+
if commands.size != 1
|
121
|
+
puts "please pass the number of production to pick"
|
122
|
+
else
|
123
|
+
number = commands[0].to_i
|
124
|
+
production = @productions[number]
|
125
|
+
unless production
|
126
|
+
puts "the production number #{commands[0]} is not known"
|
127
|
+
return true
|
128
|
+
end
|
129
|
+
unless production.finished?
|
130
|
+
puts "the production is not finished yet"
|
131
|
+
return true
|
132
|
+
end
|
133
|
+
if @configuration.store.free_capacity > 0
|
134
|
+
factory = @configuration.factory_for(production.resource)
|
135
|
+
factory.pick(production)
|
136
|
+
@productions.delete(number)
|
137
|
+
@configuration.store.put(production.resource, 1)
|
138
|
+
puts "the item was stored"
|
139
|
+
else
|
140
|
+
puts "no storage left"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
true
|
144
|
+
end
|
145
|
+
|
146
|
+
def process_production(commands)
|
147
|
+
if commands.size != 1
|
148
|
+
puts "please name the resource to produce"
|
149
|
+
else
|
150
|
+
resource_name = commands[0]
|
151
|
+
resource = @configuration.resource_by_name(resource_name)
|
152
|
+
unless resource
|
153
|
+
puts "the resource #{resource_name} is not known"
|
154
|
+
return true
|
155
|
+
end
|
156
|
+
factory = @configuration.factory_for(resource)
|
157
|
+
allocations = []
|
158
|
+
resource.dependent_resources.each do |r|
|
159
|
+
allocation = @configuration.store.allocate(r, resource.dependent_resource_amount(r))
|
160
|
+
unless allocation
|
161
|
+
puts "Not enough of #{r.name} to produce #{resource_name}"
|
162
|
+
return true
|
163
|
+
end
|
164
|
+
allocations << allocation
|
165
|
+
end
|
166
|
+
production = factory.produce(resource, allocations)
|
167
|
+
@productions[production.object_id] = production
|
168
|
+
puts "Now producing: #{production.object_id} in factory: #{factory.name}"
|
169
|
+
end
|
170
|
+
true
|
171
|
+
end
|
172
|
+
|
173
|
+
def process_resources
|
174
|
+
list_resources(@configuration.resources)
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
def process_quit
|
179
|
+
puts "Storing configuration back to file and exit."
|
180
|
+
@configurator.save
|
181
|
+
false
|
182
|
+
end
|
183
|
+
|
184
|
+
def process_save
|
185
|
+
puts "Storing configuration back to file"
|
186
|
+
@configurator.save
|
187
|
+
true
|
188
|
+
end
|
189
|
+
|
190
|
+
def process_store(commands)
|
191
|
+
if commands.size == 0
|
192
|
+
list_inventory(@configuration.store)
|
193
|
+
elsif commands.size == 3
|
194
|
+
resource = @configuration.resource_by_name(commands[1])
|
195
|
+
amount = commands[2].to_i
|
196
|
+
if resource && amount > 0
|
197
|
+
case commands[0].downcase
|
198
|
+
when "put"
|
199
|
+
puts "put #{commands[2]} units of #{commands[1]} to store"
|
200
|
+
result = @configuration.store.put(resource, amount)
|
201
|
+
puts "new stock is: #{@configuration.store.stock(resource)}"
|
202
|
+
else
|
203
|
+
puts "operator on store was not recognized"
|
204
|
+
end
|
205
|
+
else
|
206
|
+
puts "resource '#{commands[1]}' or amount '#{commands[2]}' not valid"
|
207
|
+
end
|
208
|
+
else
|
209
|
+
puts "command not recognized"
|
210
|
+
end
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
214
|
+
def process_factory(commands)
|
215
|
+
if commands.size == 0
|
216
|
+
list_factories(@configuration.factories)
|
217
|
+
else
|
218
|
+
factory = @configuration.factory(commands[0])
|
219
|
+
if factory
|
220
|
+
list_factory(factory)
|
221
|
+
else
|
222
|
+
puts "The factory '#{commands[0]}' is not registered"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Factory
|
3
|
+
attr_reader :name, :capacity, :productions, :sequential
|
4
|
+
|
5
|
+
def initialize(name, capacity, sequential=false)
|
6
|
+
@name = name
|
7
|
+
@capacity = capacity
|
8
|
+
@resources = {}
|
9
|
+
@production_times = {}
|
10
|
+
@productions = []
|
11
|
+
@sequential = sequential
|
12
|
+
end
|
13
|
+
|
14
|
+
def free_capacity
|
15
|
+
capacity - @productions.size
|
16
|
+
end
|
17
|
+
|
18
|
+
def register_resource(resource, production_time)
|
19
|
+
@production_times[resource] = production_time
|
20
|
+
@resources[resource.name] = resource
|
21
|
+
end
|
22
|
+
|
23
|
+
def resources
|
24
|
+
@production_times.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def production_time(resource)
|
28
|
+
@production_times[resource]
|
29
|
+
end
|
30
|
+
|
31
|
+
def resource_by_name(name)
|
32
|
+
@resources[name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def produce(resource, allocations=[])
|
36
|
+
unless free_capacity > 0
|
37
|
+
raise "The factory has no capacity for further production"
|
38
|
+
end
|
39
|
+
|
40
|
+
unless Factory.check_preconditions(resource, allocations)
|
41
|
+
raise "The preconditions for this production aren't met"
|
42
|
+
end
|
43
|
+
|
44
|
+
time = @production_times[resource]
|
45
|
+
unless time
|
46
|
+
raise "The resource #{resource.name} is not registered with this factory"
|
47
|
+
end
|
48
|
+
|
49
|
+
# in case of a sequential production the time must be extended
|
50
|
+
# find latest end time of a preceeding production
|
51
|
+
if sequential && latest = @productions.sort{|x,y| y.end_time <=> x.end_time}.first
|
52
|
+
time += (latest.end_time - Time.now).to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
allocations.each{ |a| a.get }
|
56
|
+
production = Production.new(resource, time)
|
57
|
+
@productions << production
|
58
|
+
production
|
59
|
+
end
|
60
|
+
|
61
|
+
def pick(production)
|
62
|
+
if production && production.finished?
|
63
|
+
@productions.delete(production)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"Factory '#{@name}' with capacity of #{@capacity} is responsible for #{@resources.keys.join(", ")}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_hash
|
72
|
+
{
|
73
|
+
"name" => @name,
|
74
|
+
"capacity" => @capacity,
|
75
|
+
"sequential" => @sequential,
|
76
|
+
"resources" => @resources.values.map{|r| r.to_hash(@production_times[r])}
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def self.check_preconditions(resource, allocations)
|
83
|
+
allocated_resources = {}
|
84
|
+
allocations.each do |a|
|
85
|
+
if a.valid?
|
86
|
+
depot = allocated_resources[a.resource] || {amount: 0}
|
87
|
+
depot[:amount] += a.amount
|
88
|
+
allocated_resources[a.resource] = depot
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
resource.dependent_resources.each do |dr|
|
93
|
+
required_amount = resource.dependent_resource_amount(dr)
|
94
|
+
depot = allocated_resources[dr]
|
95
|
+
if depot && depot[:amount] >= required_amount
|
96
|
+
depot[:amount] -= required_amount
|
97
|
+
else
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
def natural_time(time)
|
2
|
+
unit = time
|
3
|
+
|
4
|
+
# given unit in seconds
|
5
|
+
hours = unit / 3600
|
6
|
+
unit = unit - (hours * 3600)
|
7
|
+
|
8
|
+
minutes = unit / 60
|
9
|
+
seconds = unit - (minutes * 60)
|
10
|
+
|
11
|
+
result = (hours > 0) ? "#{hours}h " : ""
|
12
|
+
result += (minutes > 0) ? "#{minutes}m " : ""
|
13
|
+
result += "#{seconds}s"
|
14
|
+
|
15
|
+
result.strip
|
16
|
+
end
|
data/lib/smcty/job.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Job
|
3
|
+
attr_reader :resource, :allocation, :production, :dependent_jobs
|
4
|
+
|
5
|
+
def initialize(resource)
|
6
|
+
@resource = resource
|
7
|
+
@used = false
|
8
|
+
@dependent_jobs = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def allocate(allocation)
|
12
|
+
@allocation = allocation
|
13
|
+
@production = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def produce(production)
|
17
|
+
@allocation = nil
|
18
|
+
@production = production
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_dependent(job)
|
22
|
+
@dependent_jobs << job
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset_dependent_jobs
|
26
|
+
@dependent_jobs = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def dependencies?
|
30
|
+
@dependent_jobs.size > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def allocated?
|
34
|
+
@allocation != nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def in_production?
|
38
|
+
@production != nil && !@production.finished?
|
39
|
+
end
|
40
|
+
|
41
|
+
def ready?
|
42
|
+
@production != nil && @production.finished?
|
43
|
+
end
|
44
|
+
|
45
|
+
def new?
|
46
|
+
@allocation == nil && @production == nil && @dependent_jobs.size == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def allocated_dependencies?
|
50
|
+
if dependencies?
|
51
|
+
@dependent_jobs.each do |job|
|
52
|
+
return false unless job.allocated?
|
53
|
+
end
|
54
|
+
return true
|
55
|
+
else
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_hash
|
61
|
+
core_hash = {
|
62
|
+
"id" => self.object_id,
|
63
|
+
"resource" => @resource.name,
|
64
|
+
}
|
65
|
+
core_hash["allocation"] = @allocation.to_hash if @allocation
|
66
|
+
core_hash["production"] = @production.to_hash if @production
|
67
|
+
core_hash["dependent_jobs"] = @dependent_jobs.map{|j| j.object_id} if @dependent_jobs.size > 0
|
68
|
+
core_hash
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
data/lib/smcty/output.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
def list_inventory(store)
|
2
|
+
puts "---------------------------------------------------------------------------------------"
|
3
|
+
puts store.to_s
|
4
|
+
puts "---------------------------------------------------------------------------------------"
|
5
|
+
puts "Current inventory:"
|
6
|
+
puts ""
|
7
|
+
store.inventory.each do |item|
|
8
|
+
puts "\t#{item.name}: #{store.available_stock(item)} items (allocated: #{store.allocated_stock(item)})"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def list_resources(resource_list)
|
13
|
+
puts "-------------------------------------------------------------------"
|
14
|
+
puts "Registered Resources:"
|
15
|
+
puts ""
|
16
|
+
resource_list.each do |r|
|
17
|
+
puts "\t#{r.to_s}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def list_project(project)
|
22
|
+
puts "-------------------------------------------------------------------"
|
23
|
+
puts "Project #{project.name}"
|
24
|
+
puts ""
|
25
|
+
project.resources.each do |r|
|
26
|
+
puts "\t#{r.name} - #{project.amount(r)}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def list_factories(factory_list)
|
31
|
+
puts "-------------------------------------------------------------------"
|
32
|
+
puts "Registered Factories:"
|
33
|
+
puts ""
|
34
|
+
factory_list.each do |f|
|
35
|
+
puts "\t#{f.to_s}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def list_factory(factory)
|
40
|
+
puts "-------------------------------------------------------------------"
|
41
|
+
puts factory.to_s
|
42
|
+
puts "sequential: #{factory.sequential}"
|
43
|
+
puts "-------------------------------------------------------------------"
|
44
|
+
puts "Available resources:"
|
45
|
+
puts ""
|
46
|
+
factory.resources.each do |resource|
|
47
|
+
puts "\t#{resource.to_s} (Time: #{natural_time(factory.production_time(resource))})"
|
48
|
+
end
|
49
|
+
puts ""
|
50
|
+
puts "Running production"
|
51
|
+
factory.productions.each do |production|
|
52
|
+
line = "\t#{production.object_id} -> #{production.resource.name}"
|
53
|
+
line += " / started at: #{production.start_time}"
|
54
|
+
line += " / duration: #{production.duration}"
|
55
|
+
line += " / delivery at: #{production.start_time + production.duration}"
|
56
|
+
line + " / finished: #{production.finished?}"
|
57
|
+
puts line
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def list_configuration(configuration)
|
62
|
+
puts "==================================================================="
|
63
|
+
puts "Configuration created at: #{configuration.created_at}"
|
64
|
+
puts "==================================================================="
|
65
|
+
puts ""
|
66
|
+
list_inventory(configuration.store)
|
67
|
+
configuration.factories.each do |factory_name|
|
68
|
+
puts ""
|
69
|
+
outline_factory(configuration.factory(factory_name))
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Production
|
3
|
+
attr_reader :start_time, :duration, :resource
|
4
|
+
|
5
|
+
def initialize(resource, duration, start_time=nil)
|
6
|
+
@start_time = start_time ? Time.at(start_time) : Time.now
|
7
|
+
@duration = duration
|
8
|
+
@resource = resource
|
9
|
+
end
|
10
|
+
|
11
|
+
def finished?
|
12
|
+
end_time <= Time.now
|
13
|
+
end
|
14
|
+
|
15
|
+
def end_time
|
16
|
+
@start_time + duration
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"Production of #{resource.name} since #{@start_time} takes #{natural_time(@duration)} (Finished: #{finished?})"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
{
|
25
|
+
"start_time" => @start_time.to_i,
|
26
|
+
"duration" => @duration,
|
27
|
+
"resource" => @resource.name
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Project
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@requirements = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def resources
|
11
|
+
@requirements.keys
|
12
|
+
end
|
13
|
+
|
14
|
+
def amount(resource)
|
15
|
+
@requirements[resource]
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_requirement(resource, amount)
|
19
|
+
@requirements[resource] = amount
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Resource
|
3
|
+
attr_reader :name, :description
|
4
|
+
|
5
|
+
def initialize(name, description)
|
6
|
+
@name = name
|
7
|
+
@description = description
|
8
|
+
@dependencies = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_dependency(resource, amount)
|
12
|
+
@dependencies[resource] = amount
|
13
|
+
end
|
14
|
+
|
15
|
+
def dependent_resources
|
16
|
+
@dependencies.keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def dependent_resource_amount(resource)
|
20
|
+
@dependencies[resource]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Resource '#{@name}' (#{@description}) with these dependencies: #{dependency_list}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash(time)
|
28
|
+
{
|
29
|
+
"name" => @name,
|
30
|
+
"description" => @description,
|
31
|
+
"time" => time,
|
32
|
+
"deps" => @dependencies.keys.map{|r| {"name" => r.name, "amount" => @dependencies[r]}}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def dependency_list
|
39
|
+
items = []
|
40
|
+
@dependencies.keys.each do |resource|
|
41
|
+
items << "#{resource.name}: #{@dependencies[resource]}"
|
42
|
+
end
|
43
|
+
items.join(", ")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'smcty/job'
|
2
|
+
|
3
|
+
module Smcty
|
4
|
+
class Scheduling
|
5
|
+
|
6
|
+
def initialize(configuration)
|
7
|
+
@configuration = configuration
|
8
|
+
@projects = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def plan_project(project)
|
12
|
+
jobs = []
|
13
|
+
@projects[project] = jobs
|
14
|
+
|
15
|
+
# 1. split the requirements into jobs
|
16
|
+
project.resources.each do |r|
|
17
|
+
project.amount(r).times do
|
18
|
+
jobs << Job.new(r)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# 2. work through initial job list and either
|
23
|
+
# - allocate available resources
|
24
|
+
# - add jobs to produce dependent jobs
|
25
|
+
# and iterate until the list does not change any more.
|
26
|
+
#
|
27
|
+
prev_size = 0
|
28
|
+
while (jobs.size != prev_size) do
|
29
|
+
prev_size = jobs.size
|
30
|
+
jobs.select{|j| j.new? }.each do |job|
|
31
|
+
if store.available_stock(job.resource) >= 1
|
32
|
+
job.allocate(store.allocate(job.resource, 1))
|
33
|
+
else
|
34
|
+
job.resource.dependent_resources.each do |dr|
|
35
|
+
job.resource.dependent_resource_amount(dr).times do
|
36
|
+
djob = Job.new(dr)
|
37
|
+
job.add_dependent(djob)
|
38
|
+
jobs << djob
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def next
|
47
|
+
puts "determine the next action"
|
48
|
+
# any project finished?
|
49
|
+
action = project_ready
|
50
|
+
return action if action
|
51
|
+
# any pure request?
|
52
|
+
action = something_dependent_to_produce
|
53
|
+
return action if action
|
54
|
+
# anything to pickup?
|
55
|
+
action = something_to_pick
|
56
|
+
return action if action
|
57
|
+
# anything that can be produced?
|
58
|
+
action = something_pure_to_produce
|
59
|
+
if action
|
60
|
+
return action
|
61
|
+
else
|
62
|
+
return "wait"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def project_ready
|
67
|
+
puts " -> is there a project ready for delivery?"
|
68
|
+
@projects.keys.each do |project|
|
69
|
+
ready = true
|
70
|
+
@projects[project].each do |job|
|
71
|
+
unless job.allocated?
|
72
|
+
ready = false
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if ready
|
78
|
+
finish_project(project)
|
79
|
+
# we are done and the project can be finished.
|
80
|
+
return "finish #{project.name}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def finish_project(project)
|
87
|
+
# get all the allocated resources
|
88
|
+
@projects[project].each do |job|
|
89
|
+
job.allocation.get
|
90
|
+
end
|
91
|
+
# remove the project from scheduling
|
92
|
+
@projects.delete(project)
|
93
|
+
end
|
94
|
+
|
95
|
+
def something_pure_to_produce
|
96
|
+
puts " -> is there something without dependencies to produce?"
|
97
|
+
@projects.keys.each do |project|
|
98
|
+
@projects[project].each do |job|
|
99
|
+
if job.new?
|
100
|
+
factory = @configuration.factory_for(job.resource)
|
101
|
+
if factory.free_capacity > 0
|
102
|
+
job.produce(factory.produce(job.resource))
|
103
|
+
return "produce #{job.resource.name}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def something_to_pick
|
112
|
+
puts " -> is there something to pick?"
|
113
|
+
@projects.keys.each do |project|
|
114
|
+
@projects[project].each do |job|
|
115
|
+
if job.ready? && store.free_capacity > 0
|
116
|
+
@configuration.factory_for(job.resource).pick(job.production)
|
117
|
+
store.put(job.resource, 1)
|
118
|
+
job.allocate(store.allocate(job.resource, 1))
|
119
|
+
return "pick #{job.resource.name}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def something_dependent_to_produce
|
127
|
+
puts " -> is there something with dependencies ready for production?"
|
128
|
+
@projects.keys.each do |project|
|
129
|
+
@projects[project].each do |job|
|
130
|
+
if job.allocated_dependencies?
|
131
|
+
|
132
|
+
# extract the requirements
|
133
|
+
requirements = job.dependent_jobs.map{|j| j.allocation }
|
134
|
+
# produce the resource
|
135
|
+
|
136
|
+
factory = @configuration.factory_for(job.resource)
|
137
|
+
|
138
|
+
if factory.free_capacity > 0
|
139
|
+
puts "go for a complex production: #{job.resource}"
|
140
|
+
job.produce(factory.produce(job.resource, requirements))
|
141
|
+
|
142
|
+
# remove the dependent jobs
|
143
|
+
job.dependent_jobs.each{|j| @projects[project].delete(j)}
|
144
|
+
# reset the job dependency
|
145
|
+
job.reset_dependent_jobs
|
146
|
+
|
147
|
+
return "produce #{job.resource.name}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
# debugging
|
156
|
+
def print_job_lists
|
157
|
+
@projects.keys.each do |project|
|
158
|
+
puts
|
159
|
+
puts "Project: #{project.name}"
|
160
|
+
puts ""
|
161
|
+
@projects[project].each_with_index do |job, idx|
|
162
|
+
puts "\t Step-#{idx} - #{job_line(job)}"
|
163
|
+
end
|
164
|
+
puts
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def job_line(job)
|
169
|
+
"#{job.resource.name}: allocation: #{job.allocation} production: #{job.production} dependent: #{job.dependent_jobs.map{|j| j.resource.name}} new: #{job.new?} ready: #{job.ready?} allocated dependencies: #{job.allocated_dependencies?}"
|
170
|
+
end
|
171
|
+
# debugging
|
172
|
+
|
173
|
+
def load_project(project, job_list)
|
174
|
+
@projects[project] = job_list
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_hash
|
178
|
+
{
|
179
|
+
projects: @projects.keys.map{|p| project_hash(p, @projects[p])}
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def project_hash(project, job_list)
|
184
|
+
{
|
185
|
+
"name" => project.name,
|
186
|
+
"requirements" => project.resources.map{|r| {"resource" => r.name, "amount" => project.amount(r)}},
|
187
|
+
"jobs" => job_list.map{|j| j.to_hash}
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def store
|
194
|
+
@configuration.store
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
data/lib/smcty/store.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'smcty/allocation'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Smcty
|
5
|
+
class Store
|
6
|
+
attr_reader :name, :capacity
|
7
|
+
|
8
|
+
def initialize(name, capacity)
|
9
|
+
@name = name
|
10
|
+
@capacity = capacity
|
11
|
+
@storage = {}
|
12
|
+
@allocations = Set.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def free_capacity
|
16
|
+
@capacity - total_available_stock - total_allocated_stock
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_available_stock
|
20
|
+
result = 0
|
21
|
+
@storage.values.each do |value|
|
22
|
+
result += value
|
23
|
+
end
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def total_allocated_stock
|
28
|
+
result = 0
|
29
|
+
@allocations.each do |alloc|
|
30
|
+
result += alloc.amount
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def available_stock(resource)
|
36
|
+
@storage[resource] || 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def allocated_stock(resource)
|
40
|
+
result = 0
|
41
|
+
@allocations.each do |alloc|
|
42
|
+
result += alloc.amount if resource == alloc.resource
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def total_stock(resource)
|
48
|
+
available_stock(resource) + allocated_stock(resource)
|
49
|
+
end
|
50
|
+
|
51
|
+
def put(resource, _amount=1)
|
52
|
+
# at least one item of the resource
|
53
|
+
amount = [1, _amount].max
|
54
|
+
|
55
|
+
unless free_capacity >= amount
|
56
|
+
raise "The store is full - Cannot put #{amount} items of #{resource.name}"
|
57
|
+
end
|
58
|
+
|
59
|
+
in_stock = available_stock(resource)
|
60
|
+
@storage[resource] = in_stock + amount
|
61
|
+
amount
|
62
|
+
end
|
63
|
+
|
64
|
+
def allocate(resource, _amount)
|
65
|
+
# at least one item of the resource
|
66
|
+
amount = [1, _amount].max
|
67
|
+
|
68
|
+
in_stock = available_stock(resource)
|
69
|
+
unless in_stock >= amount
|
70
|
+
raise "#{in_stock} is not enough of #{resource.name} to allocate #{amount} items"
|
71
|
+
end
|
72
|
+
|
73
|
+
@storage[resource] = in_stock - amount
|
74
|
+
allocation = Allocation.new(self, resource, amount)
|
75
|
+
@allocations.add(allocation)
|
76
|
+
allocation
|
77
|
+
end
|
78
|
+
|
79
|
+
def get(allocation)
|
80
|
+
@allocations.delete(allocation)
|
81
|
+
end
|
82
|
+
|
83
|
+
def free(allocation)
|
84
|
+
put(allocation.resource, allocation.amount)
|
85
|
+
@allocations.delete(allocation)
|
86
|
+
end
|
87
|
+
|
88
|
+
def valid?(allocation)
|
89
|
+
@allocations.include?(allocation)
|
90
|
+
end
|
91
|
+
|
92
|
+
def inventory
|
93
|
+
@storage.keys
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
"Store #{@name} has a total capacity of #{@capacity} whereas the free capacity is #{free_capacity}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_hash
|
101
|
+
{
|
102
|
+
"name" => @name,
|
103
|
+
"capacity" => @capacity,
|
104
|
+
"stock" => @storage.keys.map{|k| {"name" => k.name, "amount" => total_stock(k)}}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def enforce(lower, amount, upper)
|
111
|
+
[[lower, amount].max, upper].min
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smcty
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Markus Kuhnt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Plan your production based on scheduled plans to optimize throughput.
|
14
|
+
email:
|
15
|
+
- markus.kuhnt@gmail.com
|
16
|
+
executables:
|
17
|
+
- smcty
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- bin/smcty
|
22
|
+
- lib/smcty.rb
|
23
|
+
- lib/smcty/allocation.rb
|
24
|
+
- lib/smcty/configuration.rb
|
25
|
+
- lib/smcty/configurator.rb
|
26
|
+
- lib/smcty/console.rb
|
27
|
+
- lib/smcty/factory.rb
|
28
|
+
- lib/smcty/helpers.rb
|
29
|
+
- lib/smcty/job.rb
|
30
|
+
- lib/smcty/output.rb
|
31
|
+
- lib/smcty/planner.rb
|
32
|
+
- lib/smcty/production.rb
|
33
|
+
- lib/smcty/project.rb
|
34
|
+
- lib/smcty/resource.rb
|
35
|
+
- lib/smcty/scheduling.rb
|
36
|
+
- lib/smcty/store.rb
|
37
|
+
- lib/smcty/version.rb
|
38
|
+
homepage:
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.3.6
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 2.4.5.1
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: A production planning software for simcity(tm)
|
62
|
+
test_files: []
|