summaryrefslogtreecommitdiff
path: root/src/item.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/item.cc')
-rw-r--r--src/item.cc467
1 files changed, 467 insertions, 0 deletions
diff --git a/src/item.cc b/src/item.cc
new file mode 100644
index 00000000..99d1d835
--- /dev/null
+++ b/src/item.cc
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) 2003-2009, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "item.h"
+#include "interactive.h"
+
+namespace ledger {
+
+bool item_t::use_effective_date = false;
+
+bool item_t::has_tag(const string& tag) const
+{
+ DEBUG("item.meta", "Checking if item has tag: " << tag);
+ if (! metadata) {
+ DEBUG("item.meta", "Item has no metadata at all");
+ return false;
+ }
+ string_map::const_iterator i = metadata->find(tag);
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("item.meta")) {
+ if (i == metadata->end())
+ DEBUG("item.meta", "Item does not have this tag");
+ else
+ DEBUG("item.meta", "Item has the tag!");
+ }
+#endif
+ return i != metadata->end();
+}
+
+bool item_t::has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask) const
+{
+ if (metadata) {
+ foreach (const string_map::value_type& data, *metadata) {
+ if (tag_mask.match(data.first)) {
+ if (! value_mask)
+ return true;
+ else if (data.second)
+ return value_mask->match(*data.second);
+ }
+ }
+ }
+ return false;
+}
+
+optional<string> item_t::get_tag(const string& tag) const
+{
+ DEBUG("item.meta", "Getting item tag: " << tag);
+ if (metadata) {
+ DEBUG("item.meta", "Item has metadata");
+ string_map::const_iterator i = metadata->find(tag);
+ if (i != metadata->end()) {
+ DEBUG("item.meta", "Found the item!");
+ return (*i).second;
+ }
+ }
+ return none;
+}
+
+optional<string> item_t::get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask) const
+{
+ if (metadata) {
+ foreach (const string_map::value_type& data, *metadata) {
+ if (tag_mask.match(data.first) &&
+ (! value_mask ||
+ (data.second && value_mask->match(*data.second))))
+ return data.second;
+ }
+ }
+ return none;
+}
+
+void item_t::set_tag(const string& tag,
+ const optional<string>& value)
+{
+ if (! metadata)
+ metadata = string_map();
+
+ DEBUG("item.meta", "Setting tag '" << tag << "' to value '"
+ << (value ? *value : string("<none>")) << "'");
+
+ std::pair<string_map::iterator, bool> result
+ = metadata->insert(string_map::value_type(tag, value));
+ assert(result.second);
+}
+
+void item_t::parse_tags(const char * p,
+ optional<date_t::year_type> current_year)
+{
+ if (const char * b = std::strchr(p, '[')) {
+ if (*(b + 1) != '\0' &&
+ (std::isdigit(*(b + 1)) || *(b + 1) == '=')) {
+ if (const char * e = std::strchr(p, ']')) {
+ char buf[256];
+ std::strncpy(buf, b + 1, e - b - 1);
+ buf[e - b - 1] = '\0';
+
+ if (char * p = std::strchr(buf, '=')) {
+ *p++ = '\0';
+ _date_eff = parse_date(p, current_year);
+ }
+ if (buf[0])
+ _date = parse_date(buf, current_year);
+ }
+ }
+ }
+
+ if (! std::strchr(p, ':'))
+ return;
+
+ scoped_array<char> buf(new char[std::strlen(p) + 1]);
+
+ std::strcpy(buf.get(), p);
+
+ string tag;
+ for (char * q = std::strtok(buf.get(), " \t");
+ q;
+ q = std::strtok(NULL, " \t")) {
+ const string::size_type len = std::strlen(q);
+ if (! tag.empty()) {
+ if (! has_tag(tag))
+ set_tag(tag, string(p + (q - buf.get())));
+ break;
+ }
+ else if (q[0] == ':' && q[len - 1] == ':') { // a series of tags
+ for (char * r = std::strtok(q + 1, ":");
+ r;
+ r = std::strtok(NULL, ":"))
+ set_tag(r);
+ }
+ else if (q[len - 1] == ':') { // a metadata setting
+ tag = string(q, len - 1);
+ }
+ }
+}
+
+void item_t::append_note(const char * p,
+ optional<date_t::year_type> current_year)
+{
+ if (note) {
+ *note += '\n';
+ *note += p;
+ } else {
+ note = p;
+ }
+
+ parse_tags(p, current_year);
+}
+
+namespace {
+ value_t get_status(item_t& item) {
+ return long(item.state());
+ }
+ value_t get_uncleared(item_t& item) {
+ return item.state() == item_t::UNCLEARED;
+ }
+ value_t get_cleared(item_t& item) {
+ return item.state() == item_t::CLEARED;
+ }
+ value_t get_pending(item_t& item) {
+ return item.state() == item_t::PENDING;
+ }
+
+ value_t get_actual(item_t& item) {
+ return ! item.has_flags(ITEM_GENERATED | ITEM_TEMP);
+ }
+
+ value_t get_date(item_t& item) {
+ return item.date();
+ }
+ value_t get_effective_date(item_t& item) {
+ if (optional<date_t> effective = item.effective_date())
+ return *effective;
+ return NULL_VALUE;
+ }
+ value_t get_note(item_t& item) {
+ return string_value(item.note ? *item.note : empty_string);
+ }
+
+ value_t has_tag(call_scope_t& args) {
+ item_t& item(find_scope<item_t>(args));
+
+ if (args.size() == 1) {
+ if (args[0].is_string())
+ return item.has_tag(args[0].as_string());
+ else if (args[0].is_mask())
+ return item.has_tag(args[0].as_mask());
+ else
+ throw_(std::runtime_error,
+ _("Expected string or mask for argument 1, but received %1")
+ << args[0].label());
+ }
+ else if (args.size() == 2) {
+ if (args[0].is_mask() && args[1].is_mask())
+ return item.has_tag(args[0].to_mask(), args[1].to_mask());
+ else
+ throw_(std::runtime_error,
+ _("Expected masks for arguments 1 and 2, but received %1 and %2")
+ << args[0].label() << args[1].label());
+ }
+ else if (args.size() == 0) {
+ throw_(std::runtime_error, _("Too few arguments to function"));
+ }
+ else {
+ throw_(std::runtime_error, _("Too many arguments to function"));
+ }
+ return false;
+ }
+
+ value_t get_tag(call_scope_t& scope) {
+ in_context_t<item_t> env(scope, "s");
+ if (optional<string> value = env->get_tag(env.get<string>(0)))
+ return string_value(*value);
+ return string_value(empty_string);
+ }
+
+ value_t get_pathname(item_t& item) {
+ if (item.pos)
+ return string_value(item.pos->pathname.string());
+ else
+ return string_value(empty_string);
+ }
+
+ value_t get_beg_pos(item_t& item) {
+ return item.pos ? long(item.pos->beg_pos) : 0L;
+ }
+
+ value_t get_beg_line(item_t& item) {
+ return item.pos ? long(item.pos->beg_line) : 0L;
+ }
+
+ value_t get_end_pos(item_t& item) {
+ return item.pos ? long(item.pos->end_pos) : 0L;
+ }
+
+ value_t get_end_line(item_t& item) {
+ return item.pos ? long(item.pos->end_line) : 0L;
+ }
+
+ value_t get_depth(item_t&) {
+ return 0L;
+ }
+
+ value_t ignore(item_t&) {
+ return false;
+ }
+
+ template <value_t (*Func)(item_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<item_t>(scope));
+ }
+}
+
+value_t get_comment(item_t& item)
+{
+ if (! item.note) {
+ return string_value("");
+ } else {
+ std::ostringstream buf;
+ if (item.note->length() > 15)
+ buf << "\n ;";
+ else
+ buf << " ;";
+
+ bool need_separator = false;
+ for (const char * p = item.note->c_str(); *p; p++) {
+ if (*p == '\n') {
+ need_separator = true;
+ } else {
+ if (need_separator) {
+ buf << "\n ;";
+ need_separator = false;
+ }
+ buf << *p;
+ }
+ }
+ return string_value(buf.str());
+ }
+}
+
+expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (kind != symbol_t::FUNCTION)
+ return NULL;
+
+ switch (name[0]) {
+ case 'a':
+ if (name == "actual")
+ return WRAP_FUNCTOR(get_wrapper<&get_actual>);
+ break;
+
+ case 'b':
+ if (name == "beg_line")
+ return WRAP_FUNCTOR(get_wrapper<&get_beg_line>);
+ else if (name == "beg_pos")
+ return WRAP_FUNCTOR(get_wrapper<&get_beg_pos>);
+ break;
+
+ case 'c':
+ if (name == "cleared")
+ return WRAP_FUNCTOR(get_wrapper<&get_cleared>);
+ else if (name == "comment")
+ return WRAP_FUNCTOR(get_wrapper<&get_comment>);
+ break;
+
+ case 'd':
+ if (name[1] == '\0' || name == "date")
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ else if (name == "depth")
+ return WRAP_FUNCTOR(get_wrapper<&get_depth>);
+ break;
+
+ case 'e':
+ if (name == "end_line")
+ return WRAP_FUNCTOR(get_wrapper<&get_end_line>);
+ else if (name == "end_pos")
+ return WRAP_FUNCTOR(get_wrapper<&get_end_pos>);
+ else if (name == "effective_date")
+ return WRAP_FUNCTOR(get_wrapper<&get_effective_date>);
+ break;
+
+ case 'f':
+ if (name == "filename")
+ return WRAP_FUNCTOR(get_wrapper<&get_pathname>);
+ break;
+
+ case 'h':
+ if (name == "has_tag")
+ return WRAP_FUNCTOR(ledger::has_tag);
+ else if (name == "has_meta")
+ return WRAP_FUNCTOR(ledger::has_tag);
+ break;
+
+ case 'i':
+ if (name == "is_account")
+ return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ break;
+
+ case 'm':
+ if (name == "meta")
+ return WRAP_FUNCTOR(ledger::get_tag);
+ break;
+
+ case 'n':
+ if (name == "note")
+ return WRAP_FUNCTOR(get_wrapper<&get_note>);
+ break;
+
+ case 'p':
+ if (name == "pending")
+ return WRAP_FUNCTOR(get_wrapper<&get_pending>);
+ else if (name == "parent")
+ return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ break;
+
+ case 's':
+ if (name == "status")
+ return WRAP_FUNCTOR(get_wrapper<&get_status>);
+ break;
+
+ case 't':
+ if (name == "tag")
+ return WRAP_FUNCTOR(ledger::get_tag);
+ break;
+
+ case 'u':
+ if (name == "uncleared")
+ return WRAP_FUNCTOR(get_wrapper<&get_uncleared>);
+ break;
+
+ case 'L':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_actual>);
+ break;
+
+ case 'X':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_cleared>);
+ break;
+
+ case 'Y':
+ if (name[1] == '\0')
+ return WRAP_FUNCTOR(get_wrapper<&get_pending>);
+ break;
+ }
+
+ return NULL;
+}
+
+bool item_t::valid() const
+{
+ if (_state != UNCLEARED && _state != CLEARED && _state != PENDING) {
+ DEBUG("ledger.validate", "item_t: state is bad");
+ return false;
+ }
+
+ return true;
+}
+
+void print_item(std::ostream& out, const item_t& item, const string& prefix)
+{
+ out << source_context(item.pos->pathname, item.pos->beg_pos,
+ item.pos->end_pos, prefix);
+}
+
+string item_context(const item_t& item, const string& desc)
+{
+ std::streamoff len = item.pos->end_pos - item.pos->beg_pos;
+ if (! len)
+ return _("<no item context>");
+
+ assert(len > 0);
+ assert(len < 2048);
+
+ std::ostringstream out;
+
+ if (item.pos->pathname == path("/dev/stdin")) {
+ out << desc << _(" from standard input:");
+ return out.str();
+ }
+
+ out << desc << _(" from \"") << item.pos->pathname.string() << "\"";
+
+ if (item.pos->beg_line != item.pos->end_line)
+ out << _(", lines ") << item.pos->beg_line << "-"
+ << item.pos->end_line << ":\n";
+ else
+ out << _(", line ") << item.pos->beg_line << ":\n";
+
+ print_item(out, item, "> ");
+
+ return out.str();
+}
+
+} // namespace ledger