TFile& TFile::AddAlias(const Stroka& name) {
    AddAliasImpl(name);

    const TStringBuf proto = STRINGBUF(".proto");
    const TStringBuf gztproto = STRINGBUF(".gztproto");

    // also add complementing .gztproto or .proto
    if (name.has_suffix(proto))
        AddAliasImpl(Stroka(TStringBuf(name).Chop(proto.size()), gztproto));
    else if (name.has_suffix(gztproto))
        AddAliasImpl(Stroka(TStringBuf(name).Chop(gztproto.size()), proto));

    return *this;
}
namespace NLastGetopt
{

static const TString DefaultHelp = "No help, sorry";
static const TStringBuf SPad = STRINGBUF("  ");



void PrintVersionAndExit(const TOptsParser*) {
    Cout << (NLastGetoptPrivate::VersionString()? NLastGetoptPrivate::VersionString() : "program version: not linked with library/getopt") << Endl;
    exit(NLastGetoptPrivate::VersionString().empty());
}

// Like TString::Quote(), but does not quote digits-only string
static TString QuoteForHelp(const TString& str) {
    if (str.empty())
        return str.Quote();
    for (size_t i = 0; i < str.size(); ++i) {
        if (!isdigit(str[i]))
            return str.Quote();
    }
    return str;
}

namespace NPrivate
{
    TString OptToString(char c) {
        TStringStream ss;
        ss << "-" << c;
        return ss.Str();
    }

    TString OptToString(const TString& longOption) {
        TStringStream ss;
        ss << "--" << longOption;
        return ss.Str();
    }

    TString OptToString(const TOpt* opt) {
        return opt->ToShortString();
    }
}

TOpts::TOpts(const TStringBuf& optstring)
    : ArgPermutation_(DEFAULT_ARG_PERMUTATION)
    , AllowSingleDashForLong_(false)
    , AllowPlusForLong_(false)
    , AllowUnknownCharOptions_(false)
    , AllowUnknownLongOptions_(false)
    , FreeArgsMin_(0)
    , FreeArgsMax_(Max<ui32>()) {
    if (!optstring.empty()) {
        AddCharOptions(optstring);
    }
    AddVersionOption(0);
}

void TOpts::AddCharOptions(const TStringBuf& optstring) {
    size_t p = 0;
    if (optstring[p] == '+') {
        ArgPermutation_ = REQUIRE_ORDER;
        ++p;
    } else if (optstring[p] == '-') {
        ArgPermutation_ = RETURN_IN_ORDER;
        ++p;
    }

    while (p < optstring.size()) {
        char c = optstring[p];
        p++;
        EHasArg ha = NO_ARGUMENT;
        if (p < optstring.size() && optstring[p] == ':') {
            ha = REQUIRED_ARGUMENT;
            p++;
        }
        if (p < optstring.size() && optstring[p] == ':') {
            ha = OPTIONAL_ARGUMENT;
            p++;
        }
        AddCharOption(c, ha);
    }
}

const TOpt* TOpts::FindLongOption(const TStringBuf& name) const {
    for (const auto& Opt : Opts_) {
        const TOpt* opt = Opt.Get();
        if (IsIn(opt->GetLongNames(), name))
            return opt;
    }
    return nullptr;
}

TOpt* TOpts::FindLongOption(const TStringBuf& name) {
    for (auto& Opt : Opts_) {
        TOpt* opt = Opt.Get();
        if (IsIn(opt->GetLongNames(), name))
            return opt;
    }
    return nullptr;
}

const TOpt* TOpts::FindCharOption(char c) const {
    for (const auto& Opt : Opts_) {
        const TOpt* opt = Opt.Get();
        if (IsIn(opt->GetShortNames(), c))
            return opt;
    }
    return nullptr;
}

TOpt* TOpts::FindCharOption(char c) {
    for (auto& Opt : Opts_) {
        TOpt* opt = Opt.Get();
        if (IsIn(opt->GetShortNames(), c))
            return opt;
    }
    return nullptr;
}

const TOpt& TOpts::GetCharOption(char c) const {
    const TOpt* option = FindCharOption(c);
    if (!option)
        ythrow TException() << "unknown char option '" << c << "'";
    return *option;
}

TOpt& TOpts::GetCharOption(char c) {
    TOpt* option = FindCharOption(c);
    if (!option)
        ythrow TException() << "unknown char option '" << c << "'";
    return *option;
}

const TOpt& TOpts::GetLongOption(const TStringBuf& name) const {
    const TOpt* option = FindLongOption(name);
    if (!option)
        ythrow TException() << "unknown option " << name;
    return *option;
}

TOpt& TOpts::GetLongOption(const TStringBuf& name) {
    TOpt* option = FindLongOption(name);
    if (!option)
        ythrow TException() << "unknown option " << name;
    return *option;
}

bool TOpts::HasAnyShortOption() const {
    for (const auto& Opt : Opts_) {
        const TOpt* opt = Opt.Get();
        if (!opt->GetShortNames().empty())
            return true;
    }
    return false;
}

bool TOpts::HasAnyLongOption() const {
    for (const auto& Opt : Opts_) {
        TOpt* opt = Opt.Get();
        if (!opt->GetLongNames().empty())
            return true;
    }
    return false;
}

void TOpts::Validate() const {
    for (TOptsVector::const_iterator i = Opts_.begin(); i != Opts_.end(); ++i) {
        TOpt* opt = i->Get();
        const TOpt::TShortNames& shortNames = opt->GetShortNames();
        for (auto c : shortNames) {
            for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
                TOpt* nextOpt = j->Get();
                if (nextOpt->CharIs(c))
                    ythrow TConfException() << "option "
                    << NPrivate::OptToString(c)
                    << " is defined more than once";
            }
        }
        const TOpt::TLongNames& longNames = opt->GetLongNames();
        for (const auto& longName : longNames) {
            for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
                TOpt* nextOpt = j->Get();
                if (nextOpt->NameIs(longName))
                    ythrow TConfException() << "option "
                    << NPrivate::OptToString(longName)
                    << " is defined more than once";
            }
        }
    }
    if (FreeArgsMax_ < FreeArgsMin_) {
        ythrow TConfException() << "FreeArgsMax must be >= FreeArgsMin";
    }
    if (!FreeArgSpecs_.empty() && FreeArgSpecs_.rbegin()->first >= FreeArgsMax_) {
        ythrow TConfException() << "Described args count is greater than FreeArgsMax. Either increase FreeArgsMax or remove unreachable descriptions";
    }
}

TOpt& TOpts::AddOption(const TOpt& option) {
    if (option.GetShortNames().empty() && option.GetLongNames().empty())
        ythrow TConfException() << "bad option: no chars, no long names";
    Opts_.push_back(new TOpt(option));
    return *Opts_.back();
}

size_t TOpts::IndexOf(const TOpt* opt) const {
    TOptsVector::const_iterator it = std::find(Opts_.begin(), Opts_.end(), opt);
    if (it == Opts_.end())
        ythrow TException() << "unknown option";
    return it - Opts_.begin();
}

const TString& TOpts::GetFreeArgTitle(size_t pos) const {
    if (FreeArgSpecs_.has(pos)) {
        const TString& title = FreeArgSpecs_.at(pos).Title;
        if (!title.Empty())
            return title;
    }
    return DefaultFreeArgSpec.Title;
}

const TString& TOpts::GetFreeArgHelp(size_t pos) const {
    if (FreeArgSpecs_.has(pos)) {
        const TString& help = FreeArgSpecs_.at(pos).Help;
        if (!help.Empty())
            return help;
    }
    return DefaultFreeArgSpec.Help;
}

void TOpts::SetFreeArgTitle(size_t pos, const TString& title, const TString& help) {
    FreeArgSpecs_[pos] = TFreeArgSpec(title, help);
}

void TOpts::SetFreeArgDefaultTitle(const TString& title, const TString& help) {
    DefaultFreeArgSpec.Title = title;
    DefaultFreeArgSpec.Help  = help;
    CustomDefaultArg_ = true;
}

static TString FormatOption(const TOpt* option, const NColorizer::TColors& colors) {
    TStringStream result;
    const TOpt::TShortNames& shorts = option->GetShortNames();
    const TOpt::TLongNames& longs = option->GetLongNames();

    const size_t nopts = shorts.size() + longs.size();
    const bool multiple = 1 < nopts;
    if (multiple)
        result << '{';
    for (size_t i = 0; i < nopts; ++i) {
        if (multiple && 0 != i)
            result << '|';

        if (i < shorts.size()) // short
            result  << colors.GreenColor() << '-' << shorts[i] << colors.OldColor();
        else
            result << colors.GreenColor() << "--" << longs[i - shorts.size()] << colors.OldColor();
    }
    if (multiple)
        result << '}';

    static const TString metavarDef("VAL");
    const TString& title = option->GetArgTitle();
    const TString& metavar = title.Empty() ? metavarDef : title;

    if (option->GetHasArg() == OPTIONAL_ARGUMENT) {
        result << " [" << metavar;
        if (option->HasOptionalValue())
            result << ':' << option->GetOptionalValue();
        result << ']';
    } else if (option->GetHasArg() == REQUIRED_ARGUMENT)
        result << ' ' << metavar;
    else
        Y_ASSERT(option->GetHasArg() == NO_ARGUMENT);

    return result.Str();
}

void TOpts::PrintCmdLine(const TStringBuf& program, IOutputStream& os, const NColorizer::TColors& colors) const {
    os << colors.BoldColor() << "Usage" << colors.OldColor() << ": " << program << " ";
    if (CustomCmdLineDescr) {
        os << CustomCmdLineDescr << Endl;
        return;
    }
    os << "[OPTIONS]";

    for (ui32 i = 0; i < FreeArgsMin_; ++i)
        os << ' ' << GetFreeArgTitle(i);

    if (FreeArgsMax_ > FreeArgsMin_) {
        if (FreeArgsMax_ == Max<ui32>()) {
            // print all described args
            for (ui32 i = FreeArgsMin_; i < FreeArgSpecs_.size(); ++i)
                os << " [" << GetFreeArgTitle(i) << "]";
            os << " [" << DefaultFreeArgSpec.Title << "]...";
        } else {
            for (ui32 i = FreeArgsMin_; i < FreeArgsMax_; ++i)
                os << " [" << GetFreeArgTitle(i) << "]";
        }
    }

    os << Endl;
}

void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& osIn, const NColorizer::TColors& colors) const {
    TStringStream os;

    if (!Title.empty())
        os << Title << "\n\n";

    PrintCmdLine(program, os, colors);

    TVector<TString> leftColumn(Opts_.size());
    TVector<size_t> leftColumnSizes(leftColumn.size());
    size_t leftWidth = 0;
    size_t requiredOptionsCount = 0;
    NColorizer::TColors disabledColors(false);

    for (size_t i = 0; i < Opts_.size(); i++) {
        const TOpt* opt = Opts_[i].Get();
        if (opt->IsHidden())
            continue;
        leftColumn[i] = FormatOption(opt, colors);
        const size_t leftColumnSize = colors.IsTTY() ? FormatOption(opt, disabledColors).size() : leftColumn[i].size();
        leftColumnSizes[i] = leftColumnSize;
        leftWidth = Max(leftWidth, leftColumnSize);
        if (opt->IsRequired())
            requiredOptionsCount++;
    }

    const size_t kMaxLeftWidth = 25;
    leftWidth = Min(leftWidth, kMaxLeftWidth);
    const TString maxPadding(kMaxLeftWidth, ' ');
    const TString leftPadding(leftWidth, ' ');

    for (size_t sectionId = 0; sectionId <= 1; sectionId++) {
        bool requiredOptionsSection = (sectionId == 0);

        if (requiredOptionsSection) {
            if (requiredOptionsCount == 0)
                continue;
            os << Endl << colors.BoldColor() << "Required parameters" << colors.OldColor() << ":" << Endl;
        } else {
            if (requiredOptionsCount == Opts_.size())
                continue;
            if (requiredOptionsCount == 0)
                os << Endl << colors.BoldColor() << "Options" << colors.OldColor() << ":" << Endl;
            else
                os << Endl << colors.BoldColor() << "Optional parameters" << colors.OldColor() << ":" << Endl;  // optional options would be a tautology
        }

        for (size_t i = 0; i < Opts_.size(); i++) {
            const TOpt* opt = Opts_[i].Get();

            if (opt->IsHidden())
                continue;
            if (opt->IsRequired() != requiredOptionsSection)
                continue;

            if (leftColumnSizes[i] > leftWidth && !opt->GetHelp().empty()) {
                os <<
                SPad << leftColumn[i] << Endl <<
                SPad << maxPadding << ' ';
            } else {
                os << SPad << leftColumn[i] << ' ';
                if (leftColumnSizes[i] < leftWidth)
                    os << TStringBuf(~leftPadding, leftWidth - leftColumnSizes[i]);
            }

            bool multiLineHelp = false;
            if (!opt->GetHelp().empty()) {
                TVector<TStringBuf> helpLines;
                Split(opt->GetHelp(), "\n", helpLines);
                multiLineHelp = (helpLines.size() > 1);
                os << helpLines[0];
                for (size_t j = 1; j < helpLines.size(); ++j) {
                    if (helpLines[j].empty())
                        continue;
                    os << Endl << SPad << leftPadding << ' ' << helpLines[j];
                }
            }

            if (opt->HasDefaultValue()) {
                TString quotedDef = QuoteForHelp(opt->GetDefaultValue());
                if (multiLineHelp)
                    os << Endl << SPad << leftPadding << " Default: " << colors.CyanColor() << quotedDef << colors.OldColor();
                else if (opt->GetHelp().empty())
                    os << "Default: " << colors.CyanColor() << quotedDef << colors.OldColor();
                else
                    os << " (default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ")";
            }

            os << Endl;
        }
    }
    PrintFreeArgsDesc(os, colors);
    osIn << os.Str();
}

void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& os) const {
    PrintUsage(program, os, NColorizer::AutoColors(os));
}

void TOpts::PrintFreeArgsDesc(IOutputStream& os, const NColorizer::TColors& colors) const {
    if (0 == FreeArgsMax_)
        return;

    size_t leftFreeWidth = 0;
    for (size_t i = 0; i < FreeArgSpecs_.size(); ++i) {
        leftFreeWidth = Max(leftFreeWidth, GetFreeArgTitle(i).size());
    }

    if (CustomDefaultArg_) {
        leftFreeWidth = Max(leftFreeWidth, DefaultFreeArgSpec.Title.size());
    }

    leftFreeWidth = Min(leftFreeWidth, size_t(30));
    os << Endl << colors.BoldColor() << "Free args"<< colors.OldColor() <<":";

    os << " min: " << colors.GreenColor() << FreeArgsMin_ << colors.OldColor() << ",";
    os << " max: " << colors.GreenColor();
    if (FreeArgsMax_ != Max<ui32>()) {
        os << FreeArgsMax_;
    } else {
        os << "unlimited";
    }
    os << colors.OldColor();

    os << " (listed described args only)" << Endl;
    const size_t limit = FreeArgSpecs_.empty() ? 0 : FreeArgSpecs_.rbegin()->first;
    for (size_t i = 0; i <= limit; ++i) {
        const TString& help = GetFreeArgHelp(i);
        os << "  " << colors.GreenColor() << RightPad(GetFreeArgTitle(i), leftFreeWidth, ' ') << colors.OldColor();

        if (!help.Empty())
            os << "  " << help;

        os << Endl;
    }

    if (CustomDefaultArg_) {
        os << "  " << colors.GreenColor() << RightPad(DefaultFreeArgSpec.Title, leftFreeWidth, ' ') << colors.OldColor();

        os << "  " << (!DefaultFreeArgSpec.Help ? DefaultHelp : DefaultFreeArgSpec.Help);

        os << Endl;
    }
}

}
namespace NLastGetopt {

static const Stroka DefaultHelp = "No help, sorry";
static const TStringBuf ExcludedShortNameChars = STRINGBUF("= -");
static const TStringBuf ExcludedLongNameChars = STRINGBUF("= ");

static const TStringBuf SPad = STRINGBUF("  ");

// Like Stroka::Quote(), but does not quote digits-only string
static Stroka QuoteForHelp(const Stroka& str) {
    if (str.empty())
        return str.Quote();
    for (size_t i = 0; i < str.size(); ++i) {
        if (!isdigit(str[i]))
            return str.Quote();
    }
    return str;
}

namespace NPrivate {
    Stroka OptToString(char c) {
        TStringStream ss;
        ss << "-" << c;
        return ss;
    }

    Stroka OptToString(const Stroka& longOption) {
        TStringStream ss;
        ss << "--" << longOption;
        return ss;
    }

    Stroka OptToString(const TOpt* opt) {
        return opt->ToShortString();
    }

}

bool TOpt::NameIs(const Stroka& name) const {
    for (yvector<Stroka>::const_iterator it = LongNames_.begin(); it != LongNames_.end(); ++it) {
        const Stroka& next = *it;
        if (next == name)
            return true;
    }
    return false;
}

bool TOpt::CharIs(char c) const {
    for (yvector<char>::const_iterator it = Chars_.begin(); it != Chars_.end(); ++it) {
        char next = *it;
        if (next == c)
            return true;
    }
    return false;
}

char TOpt::GetChar() const {
    if (Chars_.empty())
        ythrow TConfException() << "no char for option " << this->ToShortString();
    return Chars_.at(0);
}

char TOpt::GetCharOr0() const {
    if (Chars_.empty())
        return 0;
    return GetChar();
}

Stroka TOpt::GetName() const {
    if (LongNames_.empty())
        ythrow TConfException() << "no name for option " << this->ToShortString();
    return LongNames_.at(0);
}

bool TOpt::IsAllowedShortName(unsigned char c) {
    return isprint(c) && TStringBuf::npos == ExcludedShortNameChars.find(c);
}

TOpt& TOpt::AddShortName(unsigned char c) {
    if (!IsAllowedShortName(c))
        ythrow TUsageException() << "option char '" << c << "' is not allowed";
    Chars_.push_back(c);
    return *this;
}

bool TOpt::IsAllowedLongName(const Stroka& name, unsigned char* out) {
    for (size_t i = 0; i != name.size(); ++i) {
        const unsigned char c = name[i];
        if (!isprint(c) || TStringBuf::npos != ExcludedLongNameChars.find(c)) {
            if (NULL != out)
                *out = c;
            return false;
        }
    }
    return true;
}

TOpt& TOpt::AddLongName(const Stroka& name) {
    unsigned char c = 0;
    if (!IsAllowedLongName(name, &c))
        ythrow TUsageException() << "option char '" << c
            << "' in long '" << name << "' is not allowed";
    LongNames_.push_back(name);
    return *this;
}


Stroka TOpt::ToShortString() const {
    if (!LongNames_.empty())
        return NPrivate::OptToString(LongNames_.front());
    if (!Chars_.empty())
        return NPrivate::OptToString(Chars_.front());
    return "?";
}

TOpts::TOpts(const TStringBuf& optstring)
    : ArgPermutation_(DEFAULT_ARG_PERMUTATION)
    , AllowSingleDashForLong_(false)
    , AllowPlusForLong_(false)
    , AllowUnknownCharOptions_(false)
    , AllowUnknownLongOptions_(false)
    , FreeArgsMin_(0)
    , FreeArgsMax_(Max<ui32>())
{
    if (!optstring.empty()) {
        AddCharOptions(optstring);
    }
    AddVersionOption(0);
}

void TOpts::AddCharOptions(const TStringBuf& optstring) {
    size_t p = 0;
    if (optstring[p] == '+') {
        ArgPermutation_ = REQUIRE_ORDER;
        ++p;
    } else if (optstring[p] == '-') {
        ArgPermutation_ = RETURN_IN_ORDER;
        ++p;
    }

    while (p < optstring.size()) {
        char c = optstring[p];
        p++;
        EHasArg ha = NO_ARGUMENT;
        if (p < optstring.size() && optstring[p] == ':') {
            ha = REQUIRED_ARGUMENT;
            p++;
        }
        if (p < optstring.size() && optstring[p] == ':') {
            ha = OPTIONAL_ARGUMENT;
            p++;
        }
        AddCharOption(c, ha);
    }
}

const TOpt* TOpts::FindLongOption(const TStringBuf& name) const {
    for (TOptsVector::const_iterator it = Opts_.begin(); it != Opts_.end(); ++it) {
        const TOpt* opt = it->Get();
        if (IsIn(opt->GetLongNames(), name))
            return opt;
    }
    return 0;
}

TOpt* TOpts::FindLongOption(const TStringBuf& name) {
    for (TOptsVector::iterator it = Opts_.begin(); it != Opts_.end(); ++it) {
        TOpt* opt = it->Get();
        if (IsIn(opt->GetLongNames(), name))
            return opt;
    }
    return 0;
}

const TOpt* TOpts::FindCharOption(char c) const {
    for (TOptsVector::const_iterator it = Opts_.begin(); it != Opts_.end(); ++it) {
        const TOpt* opt = it->Get();
        if (IsIn(opt->GetShortNames(), c))
            return opt;
    }
    return 0;
}

TOpt* TOpts::FindCharOption(char c) {
    for (TOptsVector::iterator it = Opts_.begin(); it != Opts_.end(); ++it) {
        TOpt* opt = it->Get();
        if (IsIn(opt->GetShortNames(), c))
            return opt;
    }
    return 0;
}

const TOpt& TOpts::GetCharOption(char c) const {
    const TOpt* option = FindCharOption(c);
    if (!option)
        ythrow TException() << "unknown char option '" << c << "'";
    return *option;
}

TOpt& TOpts::GetCharOption(char c) {
    TOpt* option = FindCharOption(c);
    if (!option)
        ythrow TException() << "unknown char option '" << c << "'";
    return *option;
}

const TOpt& TOpts::GetLongOption(const TStringBuf& name) const {
    const TOpt* option = FindLongOption(name);
    if (!option)
        ythrow TException() << "unknown option " << name;
    return *option;
}

TOpt& TOpts::GetLongOption(const TStringBuf& name) {
    TOpt* option = FindLongOption(name);
    if (!option)
        ythrow TException() << "unknown option " << name;
    return *option;
}

bool TOpts::HasAnyShortOption() const {
    for (TOptsVector::const_iterator it = Opts_.begin(); it != Opts_.end(); ++it) {
        const TOpt* opt = it->Get();
        if (!opt->GetShortNames().empty())
            return true;
    }
    return false;
}

bool TOpts::HasAnyLongOption() const {
    for (TOptsVector::const_iterator it = Opts_.begin(); it != Opts_.end(); ++it) {
        TOpt* opt = it->Get();
        if (!opt->GetLongNames().empty())
            return true;
    }
    return false;
}

void TOpts::Validate() const {
    for (TOptsVector::const_iterator i = Opts_.begin(); i != Opts_.end(); ++i) {
        TOpt* opt = i->Get();
        const TOpt::TShortNames& shortNames = opt->GetShortNames();
        for (TOpt::TShortNames::const_iterator it = shortNames.begin(); it != shortNames.end(); ++it) {
            char c = *it;
            for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
                TOpt* nextOpt = j->Get();
                if (nextOpt->CharIs(c))
                    ythrow TConfException() << "option "
                        << NPrivate::OptToString(c)
                        << " is defined more than once";
            }
        }
        const TOpt::TLongNames& longNames = opt->GetLongNames();
        for (TOpt::TLongNames::const_iterator it = longNames.begin(); it != longNames.end(); ++it) {
            const Stroka& longName = *it;
            for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
                TOpt* nextOpt = j->Get();
                if (nextOpt->NameIs(longName))
                    ythrow TConfException() << "option "
                        << NPrivate::OptToString(longName)
                        << " is defined more than once";
            }
        }
    }
    if (FreeArgsMax_ < FreeArgsMin_) {
        ythrow TConfException() << "FreeArgsMax must be >= FreeArgsMin";
    }
    if (FreeArgSpecs_.size() > FreeArgsMax_) {
        ythrow TConfException() << "Described args count is greater than FreeArgsMax. Either increase FreeArgsMax or remove unreachable descriptions";
    }
}

TOpt& TOpts::AddOption(const TOpt& option) {
    if (option.GetShortNames().empty() && option.GetLongNames().empty())
        ythrow TConfException() << "bad option: no chars, no long names";
    Opts_.push_back(new TOpt(option));
    return *Opts_.back();
}

size_t TOpts::IndexOf(const TOpt* opt) const {
    TOptsVector::const_iterator it = NStl::find(Opts_.begin(), Opts_.end(), opt);
    if (it == Opts_.end())
        throw TException() << "unknown option";
    return it - Opts_.begin();
}

const Stroka& TOpts::GetFreeArgTitle(size_t pos) const {
    if (pos < FreeArgSpecs_.size()) {
        const Stroka& title = FreeArgSpecs_.at(pos).Title;
        if (!title.Empty())
            return title;
    }
    return DefaultArgTitle_;
}

const Stroka& TOpts::GetFreeArgHelp(size_t pos) const {
    if (pos < FreeArgSpecs_.size()) {
        const Stroka& help = FreeArgSpecs_.at(pos).Help;
        if (!help.Empty())
            return help;
    }
    return DefaultHelp;
}

void TOpts::SetFreeArgTitle(size_t pos, const Stroka& title, const Stroka& help) {
    if (FreeArgSpecs_.size() <= pos)
        FreeArgSpecs_.resize(pos + 1);
    FreeArgSpecs_[pos] = TFreeArgSpec(title, help);
}

void TOpts::SetFreeArgDefaultTitle(const Stroka& title, const Stroka& help) {
    DefaultArgTitle_ = title;
    DefaultArgHelp_ = help;
    CustomDefaultArg_ = true;
}

static Stroka FormatOption(const TOpt* option) {
    TStringStream result;
    const TOpt::TShortNames& shorts = option->GetShortNames();
    const TOpt::TLongNames& longs = option->GetLongNames();

    const size_t nopts = shorts.size() + longs.size();
    const bool multiple = 1 < nopts;
    if (multiple)
        result << '{';
    for (size_t i = 0; i < nopts; ++i) {
        if (multiple && 0 != i)
            result << '|';

        if (i < shorts.size()) // short
            result << '-' << shorts[i];
        else
            result << "--" << longs[i - shorts.size()];
    }
    if (multiple)
        result << '}';

    static const Stroka metavarDef("VAL");
    const Stroka& title = option->ArgTitle_;
    const Stroka& metavar = title.Empty() ? metavarDef : title;

    if (option->HasArg_ == OPTIONAL_ARGUMENT) {
        result << " [" << metavar;
        if (option->HasOptionalValue())
            result << ':' << option->GetOptionalValue();
        result << ']';
    } else if (option->HasArg_ == REQUIRED_ARGUMENT)
        result << ' ' << metavar;
    else
        YASSERT(option->HasArg_ == NO_ARGUMENT);

    return result.Str();
}

void TOpts::PrintCmdLine(const TStringBuf& program, TOutputStream& os) const {
    os << "Usage: " << program << " [OPTIONS]";

    for (ui32 i = 0; i < FreeArgsMin_; ++i)
        os << ' ' << GetFreeArgTitle(i);

    if (FreeArgsMax_ > FreeArgsMin_) {
        if (FreeArgsMax_ == Max<ui32>()) {
            // print all described args
            for (ui32 i = FreeArgsMin_; i < FreeArgSpecs_.size(); ++i)
                os << " [" << GetFreeArgTitle(i) << "]";
            os << " [" << DefaultArgTitle_ << "]...";
        } else {
            for (ui32 i = FreeArgsMin_; i < FreeArgsMax_; ++i)
                os << " [" << GetFreeArgTitle(i) << "]";
        }
    }

    os << Endl;
}

void TOpts::PrintUsage(const TStringBuf& program, TOutputStream& os) const {
    if (!Title.empty())
        os << Title << "\n\n";

    PrintCmdLine(program, os);

    yvector<Stroka> leftColumn(Opts_.size());
    size_t leftWidth = 0;
    size_t requiredOptionsCount = 0;

    for (size_t i = 0; i < Opts_.size(); i++) {
        const TOpt* opt = Opts_[i].Get();
        if (opt->IsHidden())
            continue;
        leftColumn[i] = FormatOption(opt);
        leftWidth = Max(leftWidth, leftColumn[i].size());
        if (opt->IsRequired())
            requiredOptionsCount++;
    }

    const size_t kMaxLeftWidth = 25;
    leftWidth = Min(leftWidth, kMaxLeftWidth);
    const Stroka maxPadding(kMaxLeftWidth, ' ');
    const Stroka leftPadding(leftWidth, ' ');

    for (size_t sectionId = 0; sectionId <= 1; sectionId++) {
        bool requiredOptionsSection = (sectionId == 0);

        if (requiredOptionsSection) {
            if (requiredOptionsCount == 0)
                continue;
            os << Endl << "Required parameters:" << Endl;
        } else {
            if (requiredOptionsCount == Opts_.size())
                continue;
            if (requiredOptionsCount == 0)
                os << Endl << "Options:" << Endl;
            else
                os << Endl << "Optional parameters:" << Endl;  // optional options would be a tautology
        }

        for (size_t i = 0; i < Opts_.size(); i++) {
            const TOpt* opt = Opts_[i].Get();

            if (opt->IsHidden())
                continue;
            if (opt->IsRequired() != requiredOptionsSection)
                continue;

            if (leftColumn[i].size() > leftWidth && !opt->Help_.empty())
                os <<
                    SPad << leftColumn[i] << Endl <<
                    SPad << maxPadding << ' ';
            else
                os << SPad << RightPad(leftColumn[i], leftWidth, ' ') << ' ';

            bool multiLineHelp = false;
            if (!opt->Help_.empty()) {
                yvector<TStringBuf> helpLines;
                Split(opt->Help_, "\n", helpLines);
                multiLineHelp = (helpLines.size() > 1);
                os << helpLines[0];
                for (size_t j = 1; j < helpLines.size(); ++j) {
                    if (helpLines[j].empty())
                        continue;
                    os << Endl << SPad << leftPadding << ' ' << helpLines[j];
                }
            }

            if (opt->HasDefaultValue()) {
                Stroka quotedDef = QuoteForHelp(opt->GetDefaultValue());
                if (multiLineHelp)
                    os << Endl << SPad << leftPadding << " Default: " << quotedDef;
                else if (opt->Help_.empty())
                    os << "Default: " << quotedDef;
                else
                    os << " (default: " << quotedDef << ")";
            }

            os << Endl;
        }
    }
    PrintFreeArgsDesc(os);
}

void TOpts::PrintFreeArgsDesc(TOutputStream& os) const {
    if (0 == FreeArgsMax_)
        return;

    size_t leftFreeWidth = 0;
    for (size_t i = 0; i < FreeArgSpecs_.size(); ++i) {
        leftFreeWidth = Max(leftFreeWidth, GetFreeArgTitle(i).size());
    }

    if (CustomDefaultArg_) {
        leftFreeWidth = Max(leftFreeWidth, DefaultArgTitle_.size());
    }

    leftFreeWidth = Min(leftFreeWidth, size_t(30));
    os << Endl << "Free args:";

    os << " min: " << FreeArgsMin_ << ",";
    os << " max: ";
    if (FreeArgsMax_ != Max<ui32>()) {
        os << FreeArgsMax_;
    } else {
        os << "unlimited";
    }

    os << " (listed described args only)" << Endl;
    for (size_t i = 0; i < FreeArgSpecs_.size(); ++i) {
        const Stroka& help = GetFreeArgHelp(i);
        os << "  " << RightPad(GetFreeArgTitle(i), leftFreeWidth, ' ');

        if (!help.Empty())
            os << "  " << help;

        os << Endl;
    }

    if (CustomDefaultArg_) {
        os << "  " << RightPad(DefaultArgTitle_, leftFreeWidth, ' ');

        if (!DefaultArgHelp_.Empty())
            os << "  " << (!DefaultArgHelp_ ? DefaultHelp : DefaultArgHelp_);

        os << Endl;
    }
}

void TOpt::FireHandlers(const TOptsParser* parser) const {
    for (TOptHandlers::const_iterator i = Handlers_.begin(); i != Handlers_.end(); ++i) {
        (*i)->HandleOpt(parser);
    }
}

void PrintUsageAndExit(const TOptsParser* parser) {
    parser->PrintUsage();
    exit(0);
}

void PrintVersionAndExit(const TOptsParser*) {
    int retCode = 0;
#if defined(PROGRAM_VERSION)
    Cout << PROGRAM_VERSION << Endl;
#elif defined(SVN_REVISION)
    Cout << "revision: " << SVN_REVISION << " from " << SVN_ARCROOT << " at " << SVN_TIME << Endl;
#elif defined(GIT_TAG)
    Cout << "revision: " << GIT_TAG << Endl;
#else
    Cerr << "program version: not implemented" << Endl;
    retCode = 1;
#endif
    exit(retCode);
}

void TOptsParser::Init(const TOpts* opts, int argc, const char* argv[]) {
    opts->Validate();

    Opts_ = opts;

    if (argc < 1)
        ythrow TUsageException() << "argv must have at least one argument";

    Argc_ = argc;
    Argv_ = argv;

    ProgramName_ = argv[0];

    Pos_ = 1;
    Sop_ = 0;
    CurrentOpt_ = 0;
    CurrentValue_ = 0;
    GotMinusMinus_ = false;
    Stopped_ = false;
    OptsSeen_.clear();
    OptsDefault_.clear();
}

void TOptsParser::Init(const TOpts* opts, int argc, char* argv[]) {
    Init(opts, argc, const_cast<const char**>(argv));
}

void TOptsParser::Swap(TOptsParser& that) {
    DoSwap(Opts_, that.Opts_);
    DoSwap(Argc_, that.Argc_);
    DoSwap(Argv_, that.Argv_);
    DoSwap(TempCurrentOpt_, that.TempCurrentOpt_);
    DoSwap(ProgramName_, that.ProgramName_);
    DoSwap(Pos_, that.Pos_);
    DoSwap(Sop_, that.Sop_);
    DoSwap(Stopped_, that.Stopped_);
    DoSwap(CurrentOpt_, that.CurrentOpt_);
    DoSwap(CurrentValue_, that.CurrentValue_);
    DoSwap(GotMinusMinus_, that.GotMinusMinus_);
    DoSwap(OptsSeen_, that.OptsSeen_);
}

bool TOptsParser::Commit(const TOpt* currentOpt, const TStringBuf& currentValue, size_t pos, size_t sop) {
    Pos_ = pos;
    Sop_ = sop;
    CurrentOpt_ = currentOpt;
    CurrentValue_ = currentValue;
    if (NULL != currentOpt)
        OptsSeen_.insert(currentOpt);
    return true;
}

bool TOptsParser::CommitEndOfOptions(size_t pos) {
    Pos_ = pos;
    Sop_ = 0;
    YASSERT(!CurOpt());
    YASSERT(!CurVal());

    YASSERT(!Stopped_);

    if (Opts_->FreeArgsMin_ == Opts_->FreeArgsMax_ && Argc_ - Pos_ != Opts_->FreeArgsMin_)
        ythrow TUsageException() << "required exactly " << Opts_->FreeArgsMin_ << " free args";
    else if (Argc_ - Pos_ < Opts_->FreeArgsMin_)
        ythrow TUsageException() << "required at least " << Opts_->FreeArgsMin_ << " free args";
    else if (Argc_ - Pos_ > Opts_->FreeArgsMax_)
        ythrow TUsageException() << "required at most " << Opts_->FreeArgsMax_ << " free args";

    return false;
}

bool TOptsParser::ParseUnknownShortOptWithinArg(size_t pos, size_t sop) {
    YASSERT(pos < Argc_);
    const TStringBuf arg(Argv_[pos]);
    YASSERT(sop > 0);
    YASSERT(sop < arg.length());
    YASSERT(EIO_NONE != IsOpt(arg));

    if (!Opts_->AllowUnknownCharOptions_)
        ythrow TUsageException() << "unknown option '" << EscapeC(arg[sop])
            << "' in '" << arg << "'";

    TempCurrentOpt_.Reset(new TOpt);
    TempCurrentOpt_->AddShortName(arg[sop]);

    sop += 1;

    // mimic behavior of Opt: unknown option has arg only if char is last within arg
    if (sop < arg.length()) {
        return Commit(TempCurrentOpt_.Get(), 0, pos, sop);
    }

    pos += 1;
    sop = 0;
    if (pos == Argc_ || EIO_NONE != IsOpt(Argv_[pos])) {
        return Commit(TempCurrentOpt_.Get(), 0, pos, 0);
    }

    return Commit(TempCurrentOpt_.Get(), Argv_[pos], pos + 1, 0);
}

bool TOptsParser::ParseShortOptWithinArg(size_t pos, size_t sop) {
    YASSERT(pos < Argc_);
    const TStringBuf arg(Argv_[pos]);
    YASSERT(sop > 0);
    YASSERT(sop < arg.length());
    YASSERT(EIO_NONE != IsOpt(arg));

    size_t p = sop;
    char c = arg[p];
    const TOpt* opt = Opts_->FindCharOption(c);
    if (!opt)
        return ParseUnknownShortOptWithinArg(pos, sop);
    p += 1;
    if (p == arg.length()) {
        return ParseOptParam(opt, pos + 1);
    }
    if (opt->HasArg_ == NO_ARGUMENT) {
        return Commit(opt, 0, pos, p);
    }
    return Commit(opt, arg + p, pos + 1, 0);
}

bool TOptsParser::ParseShortOptArg(size_t pos) {
    YASSERT(pos < Argc_);
    const TStringBuf arg(Argv_[pos]);
    YASSERT(EIO_NONE != IsOpt(arg));
    YASSERT(!arg.has_prefix("--"));
    return ParseShortOptWithinArg(pos, 1);
}

bool TOptsParser::ParseOptArg(size_t pos) {
    YASSERT(pos < Argc_);
    TStringBuf arg(Argv_[pos]);
    const EIsOpt eio = IsOpt(arg);
    YASSERT(EIO_NONE != eio);
    if (EIO_DDASH == eio || EIO_PLUS == eio || (Opts_->AllowSingleDashForLong_ || !Opts_->HasAnyShortOption())) {
        // long option
        bool singleCharPrefix = EIO_DDASH != eio;
        arg.Skip(singleCharPrefix ? 1 : 2);
        TStringBuf optionName = arg.NextTok('=');
        const TOpt* option = Opts_->FindLongOption(optionName);
        if (!option) {
            if (singleCharPrefix && !arg.IsInited()) {
                return ParseShortOptArg(pos);
            } else {
                ythrow TUsageException() << "unknown option '" << optionName
                    << "' in '" << Argv_[pos] << "'";
            }
        }
        if (arg.IsInited()) {
            if (option->HasArg_ == NO_ARGUMENT)
                ythrow TUsageException() << "option " << optionName << " must have no arg";
            return Commit(option, arg, pos + 1, 0);
        }
        ++pos;
        return ParseOptParam(option, pos);
    } else {
        return ParseShortOptArg(pos);
    }
}

bool TOptsParser::ParseOptParam(const TOpt* opt, size_t pos) {
    YASSERT(opt);
    if (opt->HasArg_ == NO_ARGUMENT) {
        return Commit(opt, 0, pos, 0);
    }
    if (pos == Argc_) {
        if (opt->HasArg_ == REQUIRED_ARGUMENT)
            ythrow TUsageException() << "option " << opt->ToShortString() << " must have arg";
        return Commit(opt, 0, pos, 0);
    }
    const TStringBuf arg(Argv_[pos]);
    if (!arg.has_prefix("-") || opt->HasArg_ == REQUIRED_ARGUMENT) {
        return Commit(opt, arg, pos + 1, 0);
    }
    return Commit(opt, 0, pos, 0);
}

TOptsParser::EIsOpt TOptsParser::IsOpt(const TStringBuf& arg) const {
    EIsOpt eio = EIO_NONE;
    if (1 < arg.length()) {
        switch (arg[0]) {
        default:
            break;
        case '-':
            if ('-' != arg[1])
                eio = EIO_SDASH;
            else if (2 < arg.length())
                eio = EIO_DDASH;
            break;
        case '+':
            if (Opts_->AllowPlusForLong_)
                eio = EIO_PLUS;
            break;
        }
    }
    return eio;
}

static void memrotate(void* ptr, size_t size, size_t shift) {
    TTempBuf buf(shift);
    memcpy(buf.Data(), (char*) ptr + size - shift, shift);
    memmove((char*) ptr + shift, ptr, size - shift);
    memcpy(ptr, buf.Data(), shift);
}

bool TOptsParser::ParseWithPermutation() {
    YASSERT(Sop_ == 0);
    YASSERT(Opts_->ArgPermutation_ == PERMUTE);

    const size_t p0 = Pos_;

    size_t pc = Pos_;

    for (; pc < Argc_ && EIO_NONE == IsOpt(Argv_[pc]); ++pc) {
        // count non-args
    }

    if (pc == Argc_) {
        return CommitEndOfOptions(Pos_);
    }

    Pos_ = pc;

    bool r = ParseOptArg(Pos_);
    YASSERT(r);
    while (Pos_ == pc) {
        YASSERT(Sop_ > 0);
        r = ParseShortOptWithinArg(Pos_, Sop_);
        YASSERT(r);
    }

    size_t p2 = Pos_;

    YASSERT(p2 - pc >= 1);
    YASSERT(p2 - pc <= 2);

    memrotate(Argv_ + p0, (p2 - p0) * sizeof(void*), (p2 - pc) * sizeof(void*));

    bool r2 = ParseOptArg(p0);
    YASSERT(r2);
    return r2;
}

bool TOptsParser::DoNext() {
    YASSERT(Pos_ <= Argc_);

    if (Pos_ == Argc_)
        return CommitEndOfOptions(Pos_);

    if (GotMinusMinus_ && Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
        YASSERT(Sop_ == 0);
        return Commit(0, Argv_[Pos_], Pos_ + 1, 0);
    }

    if (Sop_ > 0)
        return ParseShortOptWithinArg(Pos_, Sop_);

    size_t pos = Pos_;
    const TStringBuf arg(Argv_[pos]);
    if (EIO_NONE != IsOpt(arg)) {
        return ParseOptArg(pos);
    } else if (arg == "--") {
        if (Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
            pos += 1;
            if (pos == Argc_)
                return CommitEndOfOptions(pos);
            GotMinusMinus_ = true;
            return Commit(0, Argv_[pos], pos + 1, 0);
        } else {
            return CommitEndOfOptions(pos + 1);
        }
    } else if (Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
        return Commit(0, arg, pos + 1, 0);
    } else if (Opts_->ArgPermutation_ == REQUIRE_ORDER) {
        return CommitEndOfOptions(Pos_);
    } else {
        return ParseWithPermutation();
    }
}

bool TOptsParser::Next() {
    bool r = false;

    if (OptsDefault_.empty()) {
        CurrentOpt_ = 0;
        TempCurrentOpt_.Destroy();

        CurrentValue_ = 0;

        if (Stopped_)
            return false;

        TOptsParser copy = *this;

        r = copy.DoNext();

        Swap(copy);

        if (!r) {
            Stopped_ = true;
            // we are done; check for missing options
            Finish();
        }
    }

    if (!r && !OptsDefault_.empty()) {
        CurrentOpt_ = OptsDefault_.front();
        CurrentValue_ = CurrentOpt_->GetDefaultValue();
        OptsDefault_.pop_front();
        r = true;
    }

    if (r) {
        if (CurOpt())
            CurOpt()->FireHandlers(this);
    }

    return r;
}

void TOptsParser::Finish() {
    const TOpts::TOptsVector& optvec = Opts_->Opts_;
    if (optvec.size() == OptsSeen_.size())
        return;

    yvector<Stroka> missingLong;
    yvector<char> missingShort;

    TOpts::TOptsVector::const_iterator it;
    for (it = optvec.begin(); it != optvec.end(); ++it) {
        const TOpt* opt = (*it).Get();
        if (NULL == opt)
            continue;
        if (OptsSeen_.has(opt))
            continue;

        if (opt->IsRequired()) {
            const TOpt::TLongNames& optnames = opt->GetLongNames();
            if (!optnames.empty())
                missingLong.push_back(optnames[0]);
            else {
                const char ch = opt->GetCharOr0();
                if (0 != ch)
                    missingShort.push_back(ch);
            }
            continue;
        }

        if (opt->HasDefaultValue())
            OptsDefault_.push_back(opt);
    }

    // also indicates subsequent options, if any, haven't been seen actually
    OptsSeen_.clear();

    const size_t nmissing = missingLong.size() + missingShort.size();
    if (0 == nmissing)
        return;

    TUsageException usage;
    usage << "The following option";
    if (1 == nmissing)
        usage << " is";
    else
        usage << "s are";
    usage << " required:";
    for (size_t i = 0; i != missingLong.size(); ++i)
        usage << " --" << missingLong[i];
    for (size_t i = 0; i != missingShort.size(); ++i)
        usage << " -" << missingShort[i];
    throw usage; // don't need lineinfo, just the message
}

const TOptParseResult* TOptsParseResult::FindParseResult(const TdVec& vec, const TOpt* opt) {
    for (TdVec::const_iterator it = vec.begin(); it != vec.end(); ++it) {
        const TOptParseResult& r = *it;
        if (r.OptPtr() == opt)
            return &r;
    }
    return 0;
}

const TOptParseResult* TOptsParseResult::FindOptParseResult(const TOpt* opt, bool includeDefault) const {
    const TOptParseResult* r = FindParseResult(Opts_, opt);
    if (NULL == r && includeDefault)
        r = FindParseResult(OptsDef_, opt);
    return r;
}

const TOptParseResult* TOptsParseResult::FindLongOptParseResult(const Stroka& name, bool includeDefault) const {
    return FindOptParseResult(&Parser_->Opts_->GetLongOption(name), includeDefault);
}

const TOptParseResult* TOptsParseResult::FindCharOptParseResult(char c, bool includeDefault) const {
    return FindOptParseResult(&Parser_->Opts_->GetCharOption(c), includeDefault);
}

bool TOptsParseResult::Has(const TOpt* opt, bool includeDefault) const {
    YASSERT(opt);
    return FindOptParseResult(opt, includeDefault) != 0;
}

bool TOptsParseResult::Has(const Stroka& name, bool includeDefault) const {
    return FindLongOptParseResult(name, includeDefault) != 0;
}

bool TOptsParseResult::Has(char c, bool includeDefault) const {
    return FindCharOptParseResult(c, includeDefault) != 0;
}

const char* TOptsParseResult::Get(const TOpt* opt, bool includeDefault) const {
    YASSERT(opt);
    const TOptParseResult* r = FindOptParseResult(opt, includeDefault);
    if (!r || r->Empty()) {
        try {
            ythrow TUsageException() << "option " << opt->ToShortString() << " is unspecified";
        } catch (...) {
            HandleError();
            // unreachable
            throw;
        }
    } else {
        return r->Back();
    }
}

const char* TOptsParseResult::GetOrElse(const TOpt* opt, const char* defaultValue) const {
    YASSERT(opt);
    const TOptParseResult* r = FindOptParseResult(opt);
    if (!r || r->Empty()) {
        return defaultValue;
    } else {
        return r->Back();
    }
}

const char* TOptsParseResult::Get(const Stroka& name, bool includeDefault) const {
    return Get(&Parser_->Opts_->GetLongOption(name), includeDefault);
}

const char* TOptsParseResult::Get(char c, bool includeDefault) const {
    return Get(&Parser_->Opts_->GetCharOption(c), includeDefault);
}

const char* TOptsParseResult::GetOrElse(const Stroka& name, const char* defaultValue) const {
    if (!Has(name))
        return defaultValue;
    return Get(name);
}

const char* TOptsParseResult::GetOrElse(char c, const char* defaultValue) const {
    if (!Has(c))
        return defaultValue;
    return Get(c);
}

TOptParseResult& TOptsParseResult::OptParseResult() {
    const TOpt* opt = Parser_->CurOpt();
    YASSERT(opt);
    TdVec& opts = Parser_->IsExplicit() ? Opts_ : OptsDef_;
    if (Parser_->IsExplicit()) // default options won't appear twice
        for (TdVec::iterator it = opts.begin(); it != opts.end(); ++it)
            if (it->OptPtr() == opt)
                return *it;
    opts.push_back(TOptParseResult(opt));
    return opts.back();
}

Stroka TOptsParseResult::GetProgramName() const {
    return Parser_->ProgramName_;
}

size_t TOptsParseResult::GetFreeArgsPos() const {
    return Parser_->Pos_;
}

yvector<Stroka> TOptsParseResult::GetFreeArgs() const {
    yvector<Stroka> v;
    for (size_t i = GetFreeArgsPos(); i < Parser_->Argc_; ++i) {
        v.push_back(Parser_->Argv_[i]);
    }
    return v;
}

size_t TOptsParseResult::GetFreeArgCount() const {
    return Parser_->Argc_ - GetFreeArgsPos();
}

void TOptsParseResult::Init(const TOpts* options, int argc, const char** argv) {
    try {
        Parser_.Reset(new TOptsParser(options, argc, argv));
        while (Parser_->Next()) {
            TOptParseResult& r = OptParseResult();
            r.AddValue(Parser_->CurValOrOpt().data());
        }
    } catch (...) {
        HandleError();
    }
}

void TOptsParseResult::HandleError() const {
    Cerr << CurrentExceptionMessage() << Endl;
    if (Parser_.Get()) // parser initializing can fail (and we get here, see Init)
        Parser_->Opts_->PrintUsage(Parser_->ProgramName_);
    exit(1);
}

void TOptsParseResultException::HandleError() const {
    throw;
}

//
// TEasySetup
//
TEasySetup::TEasySetup(const TStringBuf& optstring)
    : TOpts(optstring)
{
    AddHelpOption();
}

TOpt& TEasySetup::AdjustParam(const char* longName, const char* help, const char* argName, bool required) {
    YASSERT(longName);
    TOpt& o = AddLongOption(longName);
    if (help) {
        o.Help(help);
    }
    if (argName) {
        o.RequiredArgument(argName);
    } else {
        o.HasArg(NO_ARGUMENT);
    }
    if (required) {
        o.Required();
    }
    return o;
}

TEasySetup& TEasySetup::operator()(char shortName, const char* longName, const char* help, bool required) {
    AdjustParam(longName, help, NULL, required).AddShortName(shortName);
    return *this;
}

TEasySetup& TEasySetup::operator()(char shortName, const char* longName, const char* argName, const char* help, bool required) {
    AdjustParam(longName, help, argName, required).AddShortName(shortName);
    return *this;
}

TEasySetup& TEasySetup::operator()(const char* longName, const char* help, bool required) {
    AdjustParam(longName, help, NULL, required);
    return *this;
}

TEasySetup& TEasySetup::operator()(const char* longName, const char* argName, const char* help, bool required) {
    AdjustParam(longName, help, argName, required);
    return *this;
}

}