2 #include "globalwrap.hpp"
3 #include "integer-pool.hpp"
5 #include "register-queue.hpp"
6 #include "stateobject.hpp"
16 struct run_script
: public base
18 run_script(group
& group
, std::ostream
*& _output
)
19 : base(group
, "run-script", true), in_group(group
), output(_output
)
27 void invoke(const std::string
& filename
) throw(std::bad_alloc
, std::runtime_error
)
30 (*output
) << "Syntax: run-script <scriptfile>" << std::endl
;
33 std::istream
* o
= NULL
;
35 o
= &zip::openrel(filename
, "");
36 (*output
) << "Running '" << std::string(filename
) << "'" << std::endl
;
38 while(std::getline(*o
, line
))
39 in_group
.invoke(line
);
41 } catch(std::exception
& e
) {
47 std::string
get_short_help() throw(std::bad_alloc
)
49 return "Run file as a script";
52 std::string
get_long_help() throw(std::bad_alloc
)
54 return "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in "
59 std::ostream
*& output
;
62 void default_oom_panic()
64 std::cerr
<< "PANIC: Fatal error, can't continue: Out of memory." << std::endl
;
68 threads::rlock
* global_lock
;
69 threads::rlock
& get_cmd_lock()
71 if(!global_lock
) global_lock
= new threads::rlock
;
77 std::set
<set_listener
*> callbacks
;
78 std::map
<std::string
, factory_base
*> commands
;
83 std::map
<std::string
, base
*> commands
;
84 std::set
<set
*> set_handles
;
87 typedef stateobject::type
<set
, set_internal
> set_internal_t
;
88 typedef stateobject::type
<group
, group_internal
> group_internal_t
;
91 set_listener::~set_listener()
95 void factory_base::_factory_base(set
& _set
, const std::string
& cmd
) throw(std::bad_alloc
)
97 threads::arlock
h(get_cmd_lock());
99 in_set
->do_register(commandname
= cmd
, *this);
102 factory_base::~factory_base() throw()
104 threads::arlock
h(get_cmd_lock());
106 in_set
->do_unregister(commandname
, *this);
109 void factory_base::set_died() throw()
111 //The lock is held by assumption.
115 base::base(group
& group
, const std::string
& cmd
, bool dynamic
) throw(std::bad_alloc
)
118 is_dynamic
= dynamic
;
119 threads::arlock
h(get_cmd_lock());
120 in_group
->do_register(commandname
= cmd
, *this);
123 base::~base() throw()
125 threads::arlock
h(get_cmd_lock());
127 in_group
->do_unregister(commandname
, *this);
130 void base::group_died() throw()
132 threads::arlock
h(get_cmd_lock());
134 //If dynamic, we aren't needed anymore.
135 if(is_dynamic
) delete this;
138 std::string
base::get_short_help() throw(std::bad_alloc
)
140 return "No description available";
143 std::string
base::get_long_help() throw(std::bad_alloc
)
145 return "No help available on command " + commandname
;
148 set::set() throw(std::bad_alloc
)
154 auto state
= set_internal_t::get_soft(this);
156 threads::arlock
h(get_cmd_lock());
157 //Call all DCBs on all factories.
158 for(auto i
: state
->commands
)
159 for(auto j
: state
->callbacks
)
160 j
->destroy(*this, i
.first
);
162 for(auto j
: state
->callbacks
)
164 //Notify all factories that base set died.
165 for(auto i
: state
->commands
)
166 i
.second
->set_died();
167 //We assume factories look after themselves, so we don't destroy those.
168 set_internal_t::clear(this);
171 void set::do_register(const std::string
& name
, factory_base
& cmd
) throw(std::bad_alloc
)
173 threads::arlock
h(get_cmd_lock());
174 auto& state
= set_internal_t::get(this);
175 if(state
.commands
.count(name
)) {
176 std::cerr
<< "WARNING: Command collision for " << name
<< "!" << std::endl
;
179 state
.commands
[name
] = &cmd
;
180 //Call all CCBs on this.
181 for(auto i
: state
.callbacks
)
182 i
->create(*this, name
, cmd
);
185 void set::do_unregister(const std::string
& name
, factory_base
& cmd
) throw(std::bad_alloc
)
187 threads::arlock
h(get_cmd_lock());
188 auto state
= set_internal_t::get_soft(this);
190 if(!state
->commands
.count(name
) || state
->commands
[name
] != &cmd
) return; //Not this.
191 state
->commands
.erase(name
);
192 //Call all DCBs on this.
193 for(auto i
: state
->callbacks
)
194 i
->destroy(*this, name
);
197 void set::add_callback(set_listener
& listener
)
198 throw(std::bad_alloc
)
200 threads::arlock
h(get_cmd_lock());
201 auto& state
= set_internal_t::get(this);
202 state
.callbacks
.insert(&listener
);
203 //To avoid races, call CCBs on all factories for this.
204 for(auto j
: state
.commands
)
205 listener
.create(*this, j
.first
, *j
.second
);
208 void set::drop_callback(set_listener
& listener
) throw()
210 threads::arlock
h(get_cmd_lock());
211 auto state
= set_internal_t::get_soft(this);
213 if(state
->callbacks
.count(&listener
)) {
214 //To avoid races, call DCBs on all factories for this.
215 for(auto j
: state
->commands
)
216 listener
.destroy(*this, j
.first
);
217 state
->callbacks
.erase(&listener
);
221 std::map
<std::string
, factory_base
*> set::get_commands()
223 threads::arlock
h(get_cmd_lock());
224 auto state
= set_internal_t::get_soft(this);
225 if(!state
) return std::map
<std::string
, factory_base
*>();
226 return state
->commands
;
229 group::group() throw(std::bad_alloc
)
232 oom_panic_routine
= default_oom_panic
;
234 //The builtin commands.
235 builtin
[0] = new run_script(*this, output
);
238 group::~group() throw()
240 auto state
= group_internal_t::get_soft(this);
242 threads::arlock
h(get_cmd_lock());
244 //Notify all bases that base group died.
245 //Builtin commands delete themselves on parent group dying.
246 for(auto i
: state
->commands
)
247 i
.second
->group_died();
249 //Drop all callbacks.
250 for(auto i
: state
->set_handles
)
251 i
->drop_callback(_listener
);
252 //We assume all bases that need destroying have already been destroyed.
253 group_internal_t::clear(this);
256 void group::invoke(const std::string
& cmd
) throw()
258 auto state
= group_internal_t::get_soft(this);
261 std::string cmd2
= strip_CR(cmd
);
263 //The special ? command.
264 threads::arlock
lock(get_cmd_lock());
265 for(auto i
: state
->commands
)
266 (*output
) << i
.first
<< ": " << i
.second
->get_short_help() << std::endl
;
269 if(firstchar(cmd2
) == '?') {
271 threads::arlock
lock(get_cmd_lock());
272 std::string rcmd
= cmd2
.substr(1, min(cmd2
.find_first_of(" \t"), cmd2
.length()));
273 if(firstchar(rcmd
) != '*') {
274 //This may be an alias.
275 if(aliases
.count(rcmd
)) {
277 (*output
) << rcmd
<< " is an alias for: " << std::endl
;
279 for(auto i
: aliases
[rcmd
])
280 (*output
) << "#" << (++j
) << ": " << i
<< std::endl
;
284 rcmd
= rcmd
.substr(1);
285 if(!state
->commands
.count(rcmd
))
286 (*output
) << "Unknown command '" << rcmd
<< "'" << std::endl
;
288 (*output
) << state
->commands
[rcmd
]->get_long_help() << std::endl
;
291 bool may_be_alias_expanded
= true;
292 if(firstchar(cmd2
) == '*') {
293 may_be_alias_expanded
= false;
294 cmd2
= cmd2
.substr(1);
296 //Now this gets painful as command handlers must not be invoked with lock held.
297 if(may_be_alias_expanded
) {
298 std::list
<std::string
> aexp
;
300 threads::arlock
lock(get_cmd_lock());
301 if(!aliases
.count(cmd
))
303 aexp
= aliases
[cmd2
];
311 size_t split
= cmd2
.find_first_of(" \t");
312 std::string rcmd
= cmd2
.substr(0, min(split
, cmd2
.length()));
313 std::string args
= cmd2
.substr(min(cmd2
.find_first_not_of(" \t", split
), cmd2
.length()));
316 threads::arlock
lock(get_cmd_lock());
317 if(!state
->commands
.count(rcmd
)) {
318 (*output
) << "Unknown command '" << rcmd
<< "'" << std::endl
;
321 cmdh
= state
->commands
[rcmd
];
323 if(command_stack
.count(cmd2
))
324 throw std::runtime_error("Recursive command invocation");
325 command_stack
.insert(cmd2
);
327 command_stack
.erase(cmd2
);
329 } catch(std::bad_alloc
& e
) {
331 } catch(std::exception
& e
) {
332 (*output
) << "Error: " << e
.what() << std::endl
;
333 command_stack
.erase(cmd2
);
336 } catch(std::bad_alloc
& e
) {
341 std::set
<std::string
> group::get_aliases() throw(std::bad_alloc
)
343 threads::arlock
lock(get_cmd_lock());
344 std::set
<std::string
> r
;
345 for(auto i
: aliases
)
350 std::string
group::get_alias_for(const std::string
& aname
) throw(std::bad_alloc
)
352 threads::arlock
lock(get_cmd_lock());
353 if(!valid_alias_name(aname
))
355 if(aliases
.count(aname
)) {
357 for(auto i
: aliases
[aname
])
364 void group::set_alias_for(const std::string
& aname
, const std::string
& avalue
) throw(std::bad_alloc
)
366 threads::arlock
lock(get_cmd_lock());
367 if(!valid_alias_name(aname
))
369 std::list
<std::string
> newlist
;
371 while(avitr
< avalue
.length()) {
372 size_t nextsplit
= min(avalue
.find_first_of("\n", avitr
), avalue
.length());
373 std::string x
= strip_CR(avalue
.substr(avitr
, nextsplit
- avitr
));
375 newlist
.push_back(x
);
376 avitr
= nextsplit
+ 1;
379 aliases
.erase(aname
);
381 aliases
[aname
] = newlist
;
384 bool group::valid_alias_name(const std::string
& aliasname
) throw(std::bad_alloc
)
386 if(aliasname
.length() == 0 || aliasname
[0] == '?' || aliasname
[0] == '*')
388 if(aliasname
.find_first_of(" \t") < aliasname
.length())
393 void group::do_register(const std::string
& name
, base
& cmd
) throw(std::bad_alloc
)
395 threads::arlock
h(get_cmd_lock());
396 auto& state
= group_internal_t::get(this);
397 if(state
.commands
.count(name
))
398 std::cerr
<< "WARNING: Command collision for " << name
<< "!" << std::endl
;
399 state
.commands
[name
] = &cmd
;
402 void group::do_unregister(const std::string
& name
, base
& cmd
) throw(std::bad_alloc
)
404 threads::arlock
h(get_cmd_lock());
405 auto state
= group_internal_t::get_soft(this);
407 if(!state
->commands
.count(name
) || state
->commands
[name
] != &cmd
) return;
408 state
->commands
.erase(name
);
411 void group::set_output(std::ostream
& s
)
416 void group::set_oom_panic(void (*fn
)())
419 oom_panic_routine
= fn
;
421 oom_panic_routine
= default_oom_panic
;
424 void group::add_set(set
& s
) throw(std::bad_alloc
)
426 threads::arlock
h(get_cmd_lock());
427 auto& state
= group_internal_t::get(this);
428 if(state
.set_handles
.count(&s
)) return;
430 state
.set_handles
.insert(&s
);
431 s
.add_callback(_listener
);
433 state
.set_handles
.erase(&s
);
437 void group::drop_set(set
& s
) throw()
439 threads::arlock
h(get_cmd_lock());
440 auto state
= group_internal_t::get_soft(this);
442 //Drop the callback. This unregisters all.
443 s
.drop_callback(_listener
);
444 state
->set_handles
.erase(&s
);
447 group::listener::listener(group
& _grp
)
452 group::listener::~listener()
456 void group::listener::create(set
& s
, const std::string
& name
, factory_base
& cmd
)
458 threads::arlock
h(get_cmd_lock());
462 void group::listener::destroy(set
& s
, const std::string
& name
)
464 threads::arlock
h(get_cmd_lock());
465 auto state
= group_internal_t::get_soft(&grp
);
467 state
->commands
.erase(name
);
470 void group::listener::kill(set
& s
)
472 threads::arlock
h(get_cmd_lock());
473 auto state
= group_internal_t::get_soft(&grp
);
475 state
->set_handles
.erase(&s
);
479 void invoke_fn(void (*fn
)(const std::string
& args
), const std::string
& args
)
485 void invoke_fn(void (*fn
)(), const std::string
& args
)
488 throw std::runtime_error("This command does not take arguments");
493 void invoke_fn(void (*fn
)(struct arg_filename a
), const std::string
& args
)
496 throw std::runtime_error("Filename required");