2 #include "globalwrap.hpp"
3 #include "integer-pool.hpp"
5 #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::~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 group::group() throw(std::bad_alloc
)
224 oom_panic_routine
= default_oom_panic
;
226 //The builtin commands.
227 builtin
[0] = new run_script(*this, output
);
230 group::~group() throw()
232 auto state
= group_internal_t::get_soft(this);
234 threads::arlock
h(get_cmd_lock());
236 //Notify all bases that base group died.
237 //Builtin commands delete themselves on parent group dying.
238 for(auto i
: state
->commands
)
239 i
.second
->group_died();
241 //Drop all callbacks.
242 for(auto i
: state
->set_handles
)
243 i
->drop_callback(_listener
);
244 //We assume all bases that need destroying have already been destroyed.
245 group_internal_t::clear(this);
248 void group::invoke(const std::string
& cmd
) throw()
250 auto state
= group_internal_t::get_soft(this);
253 std::string cmd2
= strip_CR(cmd
);
255 //The special ? command.
256 threads::arlock
lock(get_cmd_lock());
257 for(auto i
: state
->commands
)
258 (*output
) << i
.first
<< ": " << i
.second
->get_short_help() << std::endl
;
261 if(firstchar(cmd2
) == '?') {
263 threads::arlock
lock(get_cmd_lock());
264 std::string rcmd
= cmd2
.substr(1, min(cmd2
.find_first_of(" \t"), cmd2
.length()));
265 if(firstchar(rcmd
) != '*') {
266 //This may be an alias.
267 if(aliases
.count(rcmd
)) {
269 (*output
) << rcmd
<< " is an alias for: " << std::endl
;
271 for(auto i
: aliases
[rcmd
])
272 (*output
) << "#" << (++j
) << ": " << i
<< std::endl
;
276 rcmd
= rcmd
.substr(1);
277 if(!state
->commands
.count(rcmd
))
278 (*output
) << "Unknown command '" << rcmd
<< "'" << std::endl
;
280 (*output
) << state
->commands
[rcmd
]->get_long_help() << std::endl
;
283 bool may_be_alias_expanded
= true;
284 if(firstchar(cmd2
) == '*') {
285 may_be_alias_expanded
= false;
286 cmd2
= cmd2
.substr(1);
288 //Now this gets painful as command handlers must not be invoked with lock held.
289 if(may_be_alias_expanded
) {
290 std::list
<std::string
> aexp
;
292 threads::arlock
lock(get_cmd_lock());
293 if(!aliases
.count(cmd
))
295 aexp
= aliases
[cmd2
];
303 size_t split
= cmd2
.find_first_of(" \t");
304 std::string rcmd
= cmd2
.substr(0, min(split
, cmd2
.length()));
305 std::string args
= cmd2
.substr(min(cmd2
.find_first_not_of(" \t", split
), cmd2
.length()));
308 } catch(std::bad_alloc
& e
) {
310 } catch(std::exception
& e
) {
311 (*output
) << "Error[" << cmd2
<< "]: " << e
.what() << std::endl
;
312 command_stack
.erase(cmd2
);
315 } catch(std::bad_alloc
& e
) {
320 void group::invoke(const std::string
& cmd
, const std::string
& args
) throw()
322 auto state
= group_internal_t::get_soft(this);
325 std::string ckey
= cmd
+ " " + args
;
329 threads::arlock
lock(get_cmd_lock());
330 if(!state
->commands
.count(cmd
)) {
331 (*output
) << "Unknown command '" << cmd
<< "'" << std::endl
;
334 cmdh
= state
->commands
[cmd
];
336 if(command_stack
.count(ckey
))
337 throw std::runtime_error("Recursive command invocation");
338 command_stack
.insert(ckey
);
340 command_stack
.erase(ckey
);
342 } catch(std::bad_alloc
& e
) {
344 } catch(std::exception
& e
) {
345 (*output
) << "Error[" << ckey
<< "]: " << e
.what() << std::endl
;
346 command_stack
.erase(ckey
);
349 } catch(std::bad_alloc
& e
) {
354 std::set
<std::string
> group::get_aliases() throw(std::bad_alloc
)
356 threads::arlock
lock(get_cmd_lock());
357 std::set
<std::string
> r
;
358 for(auto i
: aliases
)
363 std::string
group::get_alias_for(const std::string
& aname
) throw(std::bad_alloc
)
365 threads::arlock
lock(get_cmd_lock());
366 if(!valid_alias_name(aname
))
368 if(aliases
.count(aname
)) {
370 for(auto i
: aliases
[aname
])
377 void group::set_alias_for(const std::string
& aname
, const std::string
& avalue
) throw(std::bad_alloc
)
379 threads::arlock
lock(get_cmd_lock());
380 if(!valid_alias_name(aname
))
382 std::list
<std::string
> newlist
;
384 while(avitr
< avalue
.length()) {
385 size_t nextsplit
= min(avalue
.find_first_of("\n", avitr
), avalue
.length());
386 std::string x
= strip_CR(avalue
.substr(avitr
, nextsplit
- avitr
));
388 newlist
.push_back(x
);
389 avitr
= nextsplit
+ 1;
392 aliases
.erase(aname
);
394 aliases
[aname
] = newlist
;
397 bool group::valid_alias_name(const std::string
& aliasname
) throw(std::bad_alloc
)
399 if(aliasname
.length() == 0 || aliasname
[0] == '?' || aliasname
[0] == '*')
401 if(aliasname
.find_first_of(" \t") < aliasname
.length())
406 void group::do_register(const std::string
& name
, base
& cmd
) throw(std::bad_alloc
)
408 threads::arlock
h(get_cmd_lock());
409 auto& state
= group_internal_t::get(this);
410 if(state
.commands
.count(name
))
411 std::cerr
<< "WARNING: Command collision for " << name
<< "!" << std::endl
;
412 state
.commands
[name
] = &cmd
;
415 void group::do_unregister(const std::string
& name
, base
& cmd
) throw(std::bad_alloc
)
417 threads::arlock
h(get_cmd_lock());
418 auto state
= group_internal_t::get_soft(this);
420 if(!state
->commands
.count(name
) || state
->commands
[name
] != &cmd
) return;
421 state
->commands
.erase(name
);
424 void group::set_output(std::ostream
& s
)
429 void group::set_oom_panic(void (*fn
)())
432 oom_panic_routine
= fn
;
434 oom_panic_routine
= default_oom_panic
;
437 void group::add_set(set
& s
) throw(std::bad_alloc
)
439 threads::arlock
h(get_cmd_lock());
440 auto& state
= group_internal_t::get(this);
441 if(state
.set_handles
.count(&s
)) return;
443 state
.set_handles
.insert(&s
);
444 s
.add_callback(_listener
);
446 state
.set_handles
.erase(&s
);
450 void group::drop_set(set
& s
) throw()
452 threads::arlock
h(get_cmd_lock());
453 auto state
= group_internal_t::get_soft(this);
455 //Drop the callback. This unregisters all.
456 s
.drop_callback(_listener
);
457 state
->set_handles
.erase(&s
);
460 group::listener::listener(group
& _grp
)
465 group::listener::~listener()
469 void group::listener::create(set
& s
, const std::string
& name
, factory_base
& cmd
)
471 threads::arlock
h(get_cmd_lock());
475 void group::listener::destroy(set
& s
, const std::string
& name
)
477 threads::arlock
h(get_cmd_lock());
478 auto state
= group_internal_t::get_soft(&grp
);
480 state
->commands
.erase(name
);
483 void group::listener::kill(set
& s
)
485 threads::arlock
h(get_cmd_lock());
486 auto state
= group_internal_t::get_soft(&grp
);
488 state
->set_handles
.erase(&s
);
492 void invoke_fn(std::function
<void(const std::string
& args
)> fn
, const std::string
& args
)
498 void invoke_fn(std::function
<void()> fn
, const std::string
& args
)
501 throw std::runtime_error("This command does not take arguments");
506 void invoke_fn(std::function
<void(struct arg_filename a
)> fn
, const std::string
& args
)
509 throw std::runtime_error("Filename required");