Blob Blame Raw


#include "tcli.h"
#include "tconvert.h"
#include <ctype.h>

using namespace std;
using namespace TCli;

//=========================================================
//
// Release
//
//---------------------------------------------------------
namespace
{

//---------------------------------------------------------

void printToonzRelease(ostream &out)
{
	out << "Toonz 7.1" << endl;
}

//---------------------------------------------------------

void printLibRelease(ostream &out)
{
	out << "Tnzcore 1.0 - " __DATE__ << endl;
}
//---------------------------------------------------------

} //namespace

//=========================================================
//
// local UsageError
//
//---------------------------------------------------------

//=========================================================
//
// local UsageElements
//
//---------------------------------------------------------

class SpecialUsageElement : public UsageElement
{
public:
	SpecialUsageElement(string name) : UsageElement(name, " "){};
	void dumpValue(ostream &) const {};
	void resetValue(){};
};

//---------------------------------------------------------

static SpecialUsageElement bra("[");
static SpecialUsageElement ket("]");
static Switcher help("-help", "Print this help page");
static Switcher release("-release", "Print the current Toonz version");
static Switcher libRelease("-librelease", "");
// hidden: print the lib version

//=========================================================
//
// helper function
//
//---------------------------------------------------------
namespace
{

//---------------------------------------------------------

void fetchElement(int index, int &argc, char *argv[])
{
	if (index >= argc)
		throw UsageError("missing argument");
	for (int i = index; i < argc - 1; i++)
		argv[i] = argv[i + 1];
	argc--;
}

//---------------------------------------------------------

void fetchElement(int &dst, int index, int &argc, char *argv[])
{
	string s = argv[index];
	if (isInt(s))
		dst = toInt(s);
	else
		throw UsageError("expected int");
	fetchElement(index, argc, argv);
}

//---------------------------------------------------------

void fetchElement(string &dst, int index, int &argc, char *argv[])
{
	char *s = argv[index];
	fetchElement(index, argc, argv);
	if (*s == '-')
		throw UsageError("expected argument");
	dst = string(s);
}

//---------------------------------------------------------

} //namespace

//=========================================================
//
// UsageElement
//
//---------------------------------------------------------

UsageElement::UsageElement(string name, string help)
	: m_name(name), m_help(help), m_selected(false)
{
}

//---------------------------------------------------------

void UsageElement::print(ostream &out) const
{
	out << m_name;
}

//---------------------------------------------------------

void UsageElement::printHelpLine(ostream &out) const
{
	out << "  " << m_name << endl;
	out << "       " << m_help << endl;
}

//=========================================================
//
// Qualifier
//
//---------------------------------------------------------

void Qualifier::print(ostream &out) const
{
	if (isSwitcher())
		out << m_name;
	else
		out << "[ " << m_name << " ]";
}

//---------------------------------------------------------

void SimpleQualifier::fetch(int index, int &argc, char *argv[])
{
	fetchElement(index, argc, argv);
}

//---------------------------------------------------------

void SimpleQualifier::dumpValue(ostream &out) const
{
	out << m_name << " = " << (isSelected() ? "on" : "off") << endl;
}

//---------------------------------------------------------

void SimpleQualifier::resetValue()
{
	m_selected = false;
}

//=========================================================
//
// Argument & MultiArgument
//
//---------------------------------------------------------

void Argument::fetch(int index, int &argc, char *argv[])
{
	if (index >= argc)
		throw UsageError("missing argument");
	if (!assign(argv[index]))
		throw UsageError(string("bad argument type :") + argv[index]);
	for (int i = index; i < argc - 1; i++)
		argv[i] = argv[i + 1];
	argc--;
}

//---------------------------------------------------------

void MultiArgument::fetch(int index, int &argc, char *argv[])
{
	if (index >= argc)
		throw UsageError("missing argument(s)");
	allocate(argc - index);
	for (m_index = 0; m_index < m_count; m_index++)
		if (!assign(argv[index + m_index]))
			throw UsageError(string("bad argument type :") + argv[index + m_index]);
	argc -= m_count;
}

//=========================================================
//
// UsageLine<T>
//
//---------------------------------------------------------

UsageLine::UsageLine()
	: m_elements(0), m_count(0)
{
}

//---------------------------------------------------------

UsageLine::~UsageLine()
{
	if (m_elements) {
		delete[] m_elements;
	}
}

//---------------------------------------------------------

UsageLine::UsageLine(const UsageLine &src)
{
	m_count = src.m_count;
	m_elements = new UsageElementPtr[m_count];
	::memcpy(m_elements, src.m_elements, m_count * sizeof(m_elements[0]));
}

//---------------------------------------------------------

UsageLine &UsageLine::operator=(const UsageLine &src)
{
	if (src.m_elements != m_elements) {
		delete[] m_elements;
		m_elements = new UsageElementPtr[src.m_count];
		::memcpy(m_elements, src.m_elements, src.m_count * sizeof(m_elements[0]));
	}
	m_count = src.m_count;
	return *this;
}

//---------------------------------------------------------

UsageLine::UsageLine(const UsageLine &src, UsageElement &elem)
{
	m_count = src.m_count;
	m_elements = new UsageElementPtr[m_count + 1];
	::memcpy(m_elements, src.m_elements, m_count * sizeof(m_elements[0]));
	m_elements[m_count++] = &elem;
}

//---------------------------------------------------------

UsageLine::UsageLine(int count)
{
	m_count = count;
	m_elements = new UsageElementPtr[m_count];
	::memset(m_elements, 0, m_count * sizeof(m_elements[0]));
}

//---------------------------------------------------------

UsageLine::UsageLine(UsageElement &elem)
{
	m_count = 1;
	m_elements = new UsageElementPtr[m_count];
	m_elements[0] = &elem;
}

//---------------------------------------------------------

UsageLine::UsageLine(UsageElement &a, UsageElement &b)
{
	m_count = 2;
	m_elements = new UsageElementPtr[m_count];
	m_elements[0] = &a;
	m_elements[1] = &b;
}

//---------------------------------------------------------

UsageLine TCli::UsageLine::operator+(UsageElement &elem)
{
	return UsageLine(*this, elem);
}

//---------------------------------------------------------

UsageLine TCli::operator+(UsageElement &a, UsageElement &b)
{
	return UsageLine(a, b);
}

//---------------------------------------------------------

UsageLine TCli::operator+(const UsageLine &a, const Optional &b)
{
	UsageLine ul(a.getCount() + b.getCount());
	int i;
	for (i = 0; i < a.getCount(); i++)
		ul[i] = a[i];
	for (int j = 0; j < b.getCount(); j++)
		ul[i++] = b[j];
	return ul;
}

//=========================================================

Optional::Optional(const UsageLine &ul)
	: UsageLine(ul.getCount() + 2)
{
	m_elements[0] = &bra;
	m_elements[m_count - 1] = &ket;
	for (int i = 0; i < ul.getCount(); i++)
		m_elements[i + 1] = ul[i];
}

//=========================================================
//
// UsageImp
//
//---------------------------------------------------------

class TCli::UsageImp
{
	string m_progName;
	vector<UsageLine> m_usageLines;
	std::map<string, Qualifier *> m_qtable;
	vector<Qualifier *> m_qlist;
	vector<Argument *> m_args;
	typedef std::map<string, Qualifier *>::iterator qiterator;
	typedef std::map<string, Qualifier *>::const_iterator const_qiterator;

	UsageLine *m_selectedUsageLine;

public:
	UsageImp(string progName);
	~UsageImp(){};
	void add(const UsageLine &);
	void addStandardUsages();

	void registerQualifier(Qualifier *q);
	void registerQualifier(string name, Qualifier *q);

	void registerArgument(Argument *arg);

	void printUsageLine(ostream &out, const UsageLine &ul) const;
	void printUsageLines(ostream &out) const;

	void print(ostream &out) const;

	void resetValues();
	void clear();
	void parse(int argc, char *argv[]);
	void fetchArguments(
		UsageLine &ul, int a, int b,
		int &argc, char *argv[]);
	bool matchSwitcher(const UsageLine &ul) const;
	bool hasSwitcher(const UsageLine &ul) const;
	bool matchArgCount(const UsageLine &ul, int a, int b, int argc) const;

	void dumpValues(ostream &out) const;

	void getArgCountRange(const UsageLine &ul, int a, int b,
						  int &min, int &max) const;

	static const int InfiniteArgCount;
};

//---------------------------------------------------------

const int UsageImp::InfiniteArgCount = 2048;

//---------------------------------------------------------

UsageImp::UsageImp(string progName)
	: m_progName(progName), m_selectedUsageLine(0)
{
	addStandardUsages();
}

//---------------------------------------------------------

void UsageImp::addStandardUsages()
{
	add(help);
	add(release);
	add(libRelease);
}

//---------------------------------------------------------

void UsageImp::clear()
{
	m_usageLines.clear();
	m_qtable.clear();
	m_qlist.clear();
	m_args.clear();
	m_selectedUsageLine = 0;
	addStandardUsages();
}

//---------------------------------------------------------

void UsageImp::registerArgument(Argument *arg)
{
	unsigned int i;
	for (i = 0; i < m_args.size() && m_args[i] != arg; i++) {
	}
	if (i == m_args.size())
		m_args.push_back(arg);
}

//---------------------------------------------------------

void UsageImp::registerQualifier(string name, Qualifier *q)
{
	m_qtable[name] = q;
	unsigned int i;
	for (i = 0; i < m_qlist.size() && m_qlist[i] != q; i++) {
	}
	if (i == m_qlist.size())
		m_qlist.push_back(q);
}

//---------------------------------------------------------

void UsageImp::registerQualifier(Qualifier *q)
{
	string str = q->getName();
	const char *s0 = str.c_str();
	while (*s0 == ' ')
		s0++;
	const char *s = s0;
	for (;;) {
		if (*s != '-') {
			assert(!"Qualifier name must start with '-'");
		}
		do
			s++;
		while (isalnum(*s));
		if (s == s0 + 1) {
			assert(!"Empty qualifier name");
		}
		string name(s0, s - s0);
		registerQualifier(name, q);
		while (*s == ' ')
			s++;
		// ignoro gli eventuali parametri
		while (isalnum(*s)) {
			while (isalnum(*s))
				s++;
			while (*s == ' ')
				s++;
		}
		if (*s != '|')
			break;
		s++;
		while (*s == ' ')
			s++;
		s0 = s;
	}
	if (*s != '\0') {
		assert(!"Unexpected character");
	}
}

//---------------------------------------------------------

void UsageImp::add(const UsageLine &ul)
{
	m_usageLines.push_back(ul);
	for (int i = 0; i < ul.getCount(); i++) {
		Qualifier *q = dynamic_cast<Qualifier *>(ul[i]);
		if (q)
			registerQualifier(q);
		Argument *a = dynamic_cast<Argument *>(ul[i]);
		if (a)
			registerArgument(a);
	}
}

//---------------------------------------------------------

void UsageImp::printUsageLine(ostream &out, const UsageLine &ul) const
{
	out << m_progName;
	for (int j = 0; j < ul.getCount(); j++)
		if (!ul[j]->isHidden()) {
			out << " ";
			ul[j]->print(out);
		}
	out << endl;
}

//---------------------------------------------------------

void UsageImp::printUsageLines(ostream &out) const
{
	bool first = true;
	for (unsigned int i = 0; i < m_usageLines.size(); i++) {
		const UsageLine &ul = m_usageLines[i];
		int j;
		for (j = 0; j < ul.getCount(); j++)
			if (!ul[j]->isHidden())
				break;
		if (j == ul.getCount())
			continue;
		out << (first ? "usage: " : "       ");
		printUsageLine(out, ul);
		first = false;
	}
	out << endl;
}

//---------------------------------------------------------

void UsageImp::print(ostream &out) const
{
	printUsageLines(out);
	out << endl;
	unsigned int i;
	for (i = 0; i < m_qlist.size(); i++)
		if (!m_qlist[i]->isHidden())
			m_qlist[i]->printHelpLine(out);
	for (i = 0; i < m_args.size(); i++)
		m_args[i]->printHelpLine(out);
	out << endl;
}

//---------------------------------------------------------

void UsageImp::parse(int argc, char *argv[])
{
	resetValues();
	if (argc == 0 || argv[0] == 0)
		throw UsageError("missing program name");
	m_progName = string(argv[0]);
	unsigned int i;
	for (i = 1; i < (unsigned int)argc;)
		if (argv[i][0] == '-') {
			string qname(argv[i]);
			qiterator qit = m_qtable.find(qname);
			if (qit == m_qtable.end()) {
				string msg = "unknown qualifier '" + qname + "'";
				throw UsageError(msg);
			}
			Qualifier *qualifier = qit->second;
			qualifier->fetch(i, argc, argv);
			qualifier->select();
		} else
			i++;

	vector<UsageLine *> usages;

	for (i = 0; i < m_usageLines.size(); i++)
		if (hasSwitcher(m_usageLines[i]) &&
			matchSwitcher(m_usageLines[i]))
			usages.push_back(&m_usageLines[i]);
	if (usages.empty())
		for (i = 0; i < m_usageLines.size(); i++)
			if (!hasSwitcher(m_usageLines[i]))
				usages.push_back(&m_usageLines[i]);
	if (usages.size() == 0)
		throw UsageError("unrecognized syntax");
	if (usages.size() > 1) {
		int min = InfiniteArgCount;
		int max = 0;
		std::vector<UsageLine *>::iterator it;
		for (it = usages.begin(); it != usages.end();) {
			UsageLine &ul = **it;
			int lmin, lmax;
			getArgCountRange(ul, 0, ul.getCount() - 1, lmin, lmax);
			min = tmin(min, lmin);
			max = tmax(max, lmax);
			if (matchArgCount(ul, 0, ul.getCount() - 1, argc - 1) == false)
				it = usages.erase(it);
			else
				it++;
		}
		if (usages.size() == 0) {
			if (argc - 1 < min)
				throw UsageError("missing argument(s)");
			else if (argc - 1 > max)
				throw UsageError("too many arguments");
			else
				throw UsageError("bad argument number");
		} else if (usages.size() > 1)
			throw UsageError("ambiguous syntax");
	}

	if (usages.size() == 1) {
		UsageLine *found = usages[0];
		if (found->getCount() > 0)
			fetchArguments(*found, 0, found->getCount() - 1, argc, argv);
		if (argc > 1)
			throw UsageError("too many arguments");
		m_selectedUsageLine = found;
	}
}

//---------------------------------------------------------

void UsageImp::fetchArguments(
	UsageLine &ul,
	int a, int b, int &argc, char *argv[])
{
	assert(0 <= a && a <= b && b < ul.getCount());
	int i;
	for (i = a; i <= b; i++) {
		if (ul[i] == &bra || ul[i]->isMultiArgument())
			break;
		if (ul[i]->isArgument()) {
			Argument *argument = dynamic_cast<Argument *>(ul[i]);
			assert(argument);
			argument->fetch(1, argc, argv);
			argument->select();
		}
	}
	if (i > b) {
	} else if (ul[i] == &bra) {
		int n = 0;
		int j;
		for (j = b; j > i && ul[j] != &ket; j--)
			if (ul[j]->isArgument())
				n++;
		assert(j > i + 1);
		if (argc - 1 > n)
			fetchArguments(ul, i + 1, j - 1, argc, argv);
		if (j + 1 <= b)
			fetchArguments(ul, j + 1, b, argc, argv);
	} else if (ul[i]->isMultiArgument()) {
		MultiArgument *argument = dynamic_cast<MultiArgument *>(ul[i]);
		assert(argument);
		int n = 0;
		for (int j = i + 1; j <= b; j++)
			if (ul[j]->isArgument())
				n++;
		int oldArgc = argc;
		argc -= n;
		argument->fetch(1, argc, argv);
		argument->select();
		argc += n;
		if (n > 0) {
			if (argc < oldArgc)
				for (int j = n; j > 0; j--)
					argv[argc - j] = argv[oldArgc - j];
			fetchArguments(ul, i + 1, b, argc, argv);
		}
	}
}

//---------------------------------------------------------

bool UsageImp::matchSwitcher(const UsageLine &ul) const
{
	for (int i = 0; i < ul.getCount(); i++)
		if (ul[i]->isSwitcher() &&
			!ul[i]->isSelected())
			return false;
	return true;
}

//---------------------------------------------------------

bool UsageImp::hasSwitcher(const UsageLine &ul) const
{
	for (int i = 0; i < ul.getCount(); i++)
		if (ul[i]->isSwitcher())
			return true;
	return false;
}

//---------------------------------------------------------

bool UsageImp::matchArgCount(const UsageLine &ul,
							 int a, int b, int argc) const
{
	int n = 0;
	int i;
	for (i = a; i <= b; i++) {
		if (ul[i] == &bra || ul[i]->isMultiArgument())
			break;
		if (ul[i]->isArgument())
			n++;
	}
	if (i > b)
		return argc == n;
	else if (ul[i] == &bra) {
		int j;
		for (j = b; j > i && ul[j] != &ket; j--)
			if (ul[j]->isArgument())
				n++;
		assert(j > i + 1);
		if (n == argc)
			return true;
		else if (n > argc)
			return false;
		else
			return matchArgCount(ul, i + 1, j - 1, argc - n);
	} else {
		assert(ul[i]->isMultiArgument());
		n++;
		for (i++; i <= b; i++)
			if (ul[i]->isArgument())
				n++;
		return argc >= n;
	}
}

//---------------------------------------------------------

void UsageImp::getArgCountRange(const UsageLine &ul, int a, int b,
								int &min, int &max) const
{
	min = max = 0;
	int n = 0;
	int i;
	for (i = a; i <= b; i++) {
		if (ul[i] == &bra || ul[i]->isMultiArgument())
			break;
		if (ul[i]->isArgument())
			n++;
	}
	if (i > b) {
		min = max = n;
		return;
	} else if (ul[i] == &bra) {
		int j;
		for (j = b; j > i && ul[j] != &ket; j--)
			if (ul[j]->isArgument())
				n++;
		assert(j > i + 1);
		min = max = n;
		int lmin, lmax;
		getArgCountRange(ul, i + 1, j - 1, lmin, lmax);
		if (lmax == InfiniteArgCount)
			max = InfiniteArgCount;
		else
			max += lmax;
	} else {
		assert(ul[i]->isMultiArgument());
		n++;
		for (i++; i <= b; i++)
			if (ul[i]->isArgument())
				n++;
		min = n;
		max = InfiniteArgCount;
	}
}

//---------------------------------------------------------

void UsageImp::dumpValues(ostream &out) const
{
	if (m_selectedUsageLine == 0)
		return;
	cout << "selected usage: ";
	printUsageLine(out, *m_selectedUsageLine);
	unsigned int i;
	for (i = 0; i < m_qlist.size(); i++)
		m_qlist[i]->dumpValue(out);
	for (i = 0; i < m_args.size(); i++)
		m_args[i]->dumpValue(out);
	out << endl
		<< endl;
}

//---------------------------------------------------------

void UsageImp::resetValues()
{
	unsigned int i;
	for (i = 0; i < m_qlist.size(); i++)
		m_qlist[i]->resetValue();
	for (i = 0; i < m_args.size(); i++)
		m_args[i]->resetValue();
}

//=========================================================
//
// Usage
//
//---------------------------------------------------------

Usage::Usage(string progName)
{
	m_imp = new UsageImp(progName);
}

Usage::~Usage()
{
	delete m_imp;
}

void Usage::add(const UsageLine &ul)
{
	m_imp->add(ul);
}

void Usage::print(ostream &out) const
{
	m_imp->print(out);
}

void Usage::dumpValues(ostream &out) const
{
	m_imp->dumpValues(out);
}

void Usage::clear()
{
	m_imp->clear();
}

bool Usage::parse(const char *argvString, ostream &err)
{
	string s = string(argvString);
	std::vector<char *> argv;
	int len = s.size();
	for (int i = 0; i < len;) {
		while (s[i] == ' ' || s[i] == '\t')
			i++;
		if (i < len)
			argv.push_back(&s[i]);
		while (i < len && !(s[i] == ' ' || s[i] == '\t'))
			i++;
		if (i < len)
			s[i++] = '\0';
	}
	return parse(argv.size(), &argv[0], err);
}

bool Usage::parse(int argc, char *argv[], ostream &err)
{
	try {
		m_imp->parse(argc, argv);
		if (help) {
			print(err);
			return false;
		}
		if (release) {
			printToonzRelease(err);
			return false;
		}
		if (libRelease) {
			printLibRelease(err);
			return false;
		}
		return true;
	} catch (UsageError e) {
		err << "Usage error: " << e.getError() << endl;
		m_imp->printUsageLines(err);
		return false;
	}
}

//=========================================================
//
// RangeQualifier
//
//---------------------------------------------------------

RangeQualifier::RangeQualifier()
	: Qualifier("-range from to | -frame fr",
				"frame range"),
	  m_from(0), m_to(-1)
{
}

//---------------------------------------------------------

void RangeQualifier::fetch(int index, int &argc, char *argv[])
{
	assert(index < argc);
	string qname(argv[index]);
	fetchElement(index, argc, argv);
	if (qname == "-range") {
		fetchElement(m_from, index, argc, argv);
		fetchElement(m_to, index, argc, argv);
	} else if (qname == "-frame") {
		fetchElement(m_from, index, argc, argv);
		m_to = m_from;
	} else {
		assert(0);
	}
}

ostream &operator<<(ostream &out, const RangeQualifier &range)
{
	return out << "[" << range.getFrom() << ", " << range.getTo() << "]";
}

//---------------------------------------------------------

void RangeQualifier::dumpValue(ostream &out) const
{
	out << m_name << " = ";
	if (m_from <= m_to)
		out << m_from << ", " << m_to;
	else
		out << "undefined";
	out << endl;
}

//---------------------------------------------------------

void RangeQualifier::resetValue()
{
	m_from = 0;
	m_to = -1;
	m_selected = false;
}