2 #include "globalwrap.hpp"
3 #include "integer-pool.hpp"
5 #include "stateobject.hpp"
17 struct run_script
: public base
19 run_script(group
& group
, std::ostream
*& _output
)
20 : base(group
, "run-script", true), in_group(group
), output(_output
)
28 void invoke(const std::string
& filename
) throw(std::bad_alloc
, std::runtime_error
)
31 (*output
) << "Syntax: run-script <scriptfile>" << std::endl
;
34 std::istream
* o
= NULL
;
36 o
= &zip::openrel(filename
, "");
37 (*output
) << "Running '" << std::string(filename
) << "'" << std::endl
;
39 while(std::getline(*o
, line
))
40 in_group
.invoke(line
);
42 } catch(std::exception
& e
) {
48 std::string
get_short_help() throw(std::bad_alloc
)
50 return "Run file as a script";
53 std::string
get_long_help() throw(std::bad_alloc
)
55 return "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in "
60 std::ostream
*& output
;
63 void default_oom_panic()
65 std::cerr
<< "PANIC: Fatal error, can't continue: Out of memory." << std::endl
;
69 threads::rlock
* global_lock
;
70 threads::rlock
& get_cmd_lock()
72 if(!global_lock
) global_lock
= new threads::rlock
;
78 std::set
<set::listener
*> callbacks
;
79 std::map
<std::string
, factory_base
*> commands
;
84 std::map
<std::string
, base
*> commands
;
85 std::set
<set
*> set_handles
;
88 typedef stateobject::type
<set
, set_internal
> set_internal_t
;
89 typedef stateobject::type
<group
, group_internal
> group_internal_t
;
92 set::listener::~listener()
96 void factory_base::_factory_base(set
& _set
, const std::string
& cmd
) throw(std::bad_alloc
)
98 threads::arlock
h(get_cmd_lock());
100 in_set
->do_register(commandname
= cmd
, *this);
103 factory_base::~factory_base() throw()
105 threads::arlock
h(get_cmd_lock());
107 in_set
->do_unregister(commandname
, *this);
110 void factory_base::set_died() throw()
112 //The lock is held by assumption.
116 base::base(group
& group
, const std::string
& cmd
, bool dynamic
) throw(std::bad_alloc
)
119 is_dynamic
= dynamic
;
120 threads::arlock
h(get_cmd_lock());
121 in_group
->do_register(commandname
= cmd
, *this);
124 base::~base() throw()
126 threads::arlock
h(get_cmd_lock());
128 in_group
->do_unregister(commandname
, *this);
131 void base::group_died() throw()
133 threads::arlock
h(get_cmd_lock());
135 //If dynamic, we aren't needed anymore.
136 if(is_dynamic
) delete this;
139 std::string
base::get_short_help() throw(std::bad_alloc
)
141 return "No description available";
144 std::string
base::get_long_help() throw(std::bad_alloc
)
146 return "No help available on command " + commandname
;
149 set::set() throw(std::bad_alloc
)
155 auto state
= set_internal_t::get_soft(this);
157 threads::arlock
h(get_cmd_lock());
158 //Call all DCBs on all factories.
159 for(auto i
: state
->commands
)
160 for(auto j
: state
->callbacks
)
161 j
->destroy(*this, i
.first
);
163 for(auto j
: state
->callbacks
)
165 //Notify all factories that base set died.
166 for(auto i
: state
->commands
)
167 i
.second
->set_died();
168 //We assume factories look after themselves, so we don't destroy those.
169 set_internal_t::clear(this);
172 void set::do_register(const std::string
& name
, factory_base
& cmd
) throw(std::bad_alloc
)
174 threads::arlock
h(get_cmd_lock());
175 auto& state
= set_internal_t::get(this);
176 if(state
.commands
.count(name
)) {
177 std::cerr
<< "WARNING: Command collision for " << name
<< "!" << std::endl
;
180 state
.commands
[name
] = &cmd
;
181 //Call all CCBs on this.
182 for(auto i
: state
.callbacks
)
183 i
->create(*this, name
, cmd
);
186 void set::do_unregister(const std::string
& name
, factory_base
& cmd
) throw(std::bad_alloc
)
188 threads::arlock
h(get_cmd_lock());
189 auto state
= set_internal_t::get_soft(this);
191 if(!state
->commands
.count(name
) || state
->commands
[name
] != &cmd
) return; //Not this.
192 state
->commands
.erase(name
);
193 //Call all DCBs on this.
194 for(auto i
: state
->callbacks
)
195 i
->destroy(*this, name
);
198 void set::add_callback(set::listener
& listener
)
199 throw(std::bad_alloc
)
201 threads::arlock
h(get_cmd_lock());
202 auto& state
= set_internal_t::get(this);
203 state
.callbacks
.insert(&listener
);
204 //To avoid races, call CCBs on all factories for this.
205 for(auto j
: state
.commands
)
206 listener
.create(*this, j
.first
, *j
.second
);
209 void set::drop_callback(set::listener
& listener
) throw()
211 threads::arlock
h(get_cmd_lock());
212 auto state
= set_internal_t::get_soft(this);
214 if(state
->callbacks
.count(&listener
)) {
215 //To avoid races, call DCBs on all factories for this.
216 for(auto j
: state
->commands
)
217 listener
.destroy(*this, j
.first
);
218 state
->callbacks
.erase(&listener
);
222 group::group() throw(std::bad_alloc
)
225 oom_panic_routine
= default_oom_panic
;
227 //The builtin commands.
228 builtin
[0] = new run_script(*this, output
);
231 group::~group() throw()
233 auto state
= group_internal_t::get_soft(this);
235 threads::arlock
h(get_cmd_lock());
237 //Notify all bases that base group died.
238 //Builtin commands delete themselves on parent group dying.
239 for(auto i
: state
->commands
)
240 i
.second
->group_died();
242 //Drop all callbacks.
243 for(auto i
: state
->set_handles
)
244 i
->drop_callback(_listener
);
245 //We assume all bases that need destroying have already been destroyed.
246 group_internal_t::clear(this);
249 void group::invoke(const std::string
& cmd
) throw()
251 auto state
= group_internal_t::get_soft(this);
254 std::string cmd2
= strip_CR(cmd
);
256 //The special ? command.
257 threads::arlock
lock(get_cmd_lock());
258 for(auto i
: state
->commands
)
259 (*output
) << i
.first
<< ": " << i
.second
->get_short_help() << std::endl
;
262 if(firstchar(cmd2
) == '?') {
264 threads::arlock
lock(get_cmd_lock());
265 std::string rcmd
= cmd2
.substr(1, min(cmd2
.find_first_of(" \t"), cmd2
.length()));
266 if(firstchar(rcmd
) != '*') {
267 //This may be an alias.
268 if(aliases
.count(rcmd
)) {
270 (*output
) << rcmd
<< " is an alias for: " << std::endl
;
272 for(auto i
: aliases
[rcmd
])
273 (*output
) << "#" << (++j
) << ": " << i
<< std::endl
;
277 rcmd
= rcmd
.substr(1);
278 if(!state
->commands
.count(rcmd
))
279 (*output
) << "Unknown command '" << rcmd
<< "'" << std::endl
;
281 (*output
) << state
->commands
[rcmd
]->get_long_help() << std::endl
;
284 bool may_be_alias_expanded
= true;
285 if(firstchar(cmd2
) == '*') {
286 may_be_alias_expanded
= false;
287 cmd2
= cmd2
.substr(1);
289 //Now this gets painful as command handlers must not be invoked with lock held.
290 if(may_be_alias_expanded
) {
291 std::list
<std::string
> aexp
;
293 threads::arlock
lock(get_cmd_lock());
294 if(!aliases
.count(cmd
))
296 aexp
= aliases
[cmd2
];
304 size_t split
= cmd2
.find_first_of(" \t");
305 std::string rcmd
= cmd2
.substr(0, min(split
, cmd2
.length()));
306 std::string args
= cmd2
.substr(min(cmd2
.find_first_not_of(" \t", split
), cmd2
.length()));
309 } catch(std::bad_alloc
& e
) {
311 } catch(std::exception
& e
) {
312 (*output
) << "Error[" << cmd2
<< "]: " << e
.what() << std::endl
;
313 command_stack
.erase(cmd2
);
316 } catch(std::bad_alloc
& e
) {
321 void group::invoke(const std::string
& cmd
, const std::string
& args
) throw()
323 auto state
= group_internal_t::get_soft(this);
326 std::string ckey
= cmd
+ " " + args
;
330 threads::arlock
lock(get_cmd_lock());
331 if(!state
->commands
.count(cmd
)) {
332 (*output
) << "Unknown command '" << cmd
<< "'" << std::endl
;
335 cmdh
= state
->commands
[cmd
];
337 if(command_stack
.count(ckey
))
338 throw std::runtime_error("Recursive command invocation");
339 command_stack
.insert(ckey
);
341 command_stack
.erase(ckey
);
343 } catch(std::bad_alloc
& e
) {
345 } catch(std::exception
& e
) {
346 (*output
) << "Error[" << ckey
<< "]: " << e
.what() << std::endl
;
347 command_stack
.erase(ckey
);
350 } catch(std::bad_alloc
& e
) {
355 std::set
<std::string
> group::get_aliases() throw(std::bad_alloc
)
357 threads::arlock
lock(get_cmd_lock());
358 std::set
<std::string
> r
;
359 for(auto i
: aliases
)
364 std::string
group::get_alias_for(const std::string
& aname
) throw(std::bad_alloc
)
366 threads::arlock
lock(get_cmd_lock());
367 if(!valid_alias_name(aname
))
369 if(aliases
.count(aname
)) {
371 for(auto i
: aliases
[aname
])
378 void group::set_alias_for(const std::string
& aname
, const std::string
& avalue
) throw(std::bad_alloc
)
380 threads::arlock
lock(get_cmd_lock());
381 if(!valid_alias_name(aname
))
383 std::list
<std::string
> newlist
;
385 while(avitr
< avalue
.length()) {
386 size_t nextsplit
= min(avalue
.find_first_of("\n", avitr
), avalue
.length());
387 std::string x
= strip_CR(avalue
.substr(avitr
, nextsplit
- avitr
));
389 newlist
.push_back(x
);
390 avitr
= nextsplit
+ 1;
393 aliases
.erase(aname
);
395 aliases
[aname
] = newlist
;
398 bool group::valid_alias_name(const std::string
& aliasname
) throw(std::bad_alloc
)
400 if(aliasname
.length() == 0 || aliasname
[0] == '?' || aliasname
[0] == '*')
402 if(aliasname
.find_first_of(" \t") < aliasname
.length())
407 void group::do_register(const std::string
& name
, base
& cmd
) throw(std::bad_alloc
)
409 threads::arlock
h(get_cmd_lock());
410 auto& state
= group_internal_t::get(this);
411 if(state
.commands
.count(name
))
412 std::cerr
<< "WARNING: Command collision for " << name
<< "!" << std::endl
;
413 state
.commands
[name
] = &cmd
;
416 void group::do_unregister(const std::string
& name
, base
& cmd
) throw(std::bad_alloc
)
418 threads::arlock
h(get_cmd_lock());
419 auto state
= group_internal_t::get_soft(this);
421 if(!state
->commands
.count(name
) || state
->commands
[name
] != &cmd
) return;
422 state
->commands
.erase(name
);
425 void group::set_output(std::ostream
& s
)
430 void group::set_oom_panic(void (*fn
)())
433 oom_panic_routine
= fn
;
435 oom_panic_routine
= default_oom_panic
;
438 void group::add_set(set
& s
) throw(std::bad_alloc
)
440 threads::arlock
h(get_cmd_lock());
441 auto& state
= group_internal_t::get(this);
442 if(state
.set_handles
.count(&s
)) return;
444 state
.set_handles
.insert(&s
);
445 s
.add_callback(_listener
);
447 state
.set_handles
.erase(&s
);
451 void group::drop_set(set
& s
) throw()
453 threads::arlock
h(get_cmd_lock());
454 auto state
= group_internal_t::get_soft(this);
456 //Drop the callback. This unregisters all.
457 s
.drop_callback(_listener
);
458 state
->set_handles
.erase(&s
);
461 group::listener::listener(group
& _grp
)
466 group::listener::~listener()
470 void group::listener::create(set
& s
, const std::string
& name
, factory_base
& cmd
)
472 threads::arlock
h(get_cmd_lock());
476 void group::listener::destroy(set
& s
, const std::string
& name
)
478 threads::arlock
h(get_cmd_lock());
479 auto state
= group_internal_t::get_soft(&grp
);
481 state
->commands
.erase(name
);
484 void group::listener::kill(set
& s
)
486 threads::arlock
h(get_cmd_lock());
487 auto state
= group_internal_t::get_soft(&grp
);
489 state
->set_handles
.erase(&s
);
493 void invoke_fn(std::function
<void(const std::string
& args
)> fn
, const std::string
& args
)
499 void invoke_fn(std::function
<void()> fn
, const std::string
& args
)
502 throw std::runtime_error("This command does not take arguments");
507 void invoke_fn(std::function
<void(struct arg_filename a
)> fn
, const std::string
& args
)
510 throw std::runtime_error("Filename required");