[california/wip/725764-ics] Parse command-line options and parse .ics file
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/725764-ics] Parse command-line options and parse .ics file
- Date: Sat, 12 Apr 2014 02:31:59 +0000 (UTC)
commit e2e942d2ab75e10ee9627255a7e3b313434b0ace
Author: Jim Nelson <jim yorba org>
Date: Fri Apr 11 19:31:34 2014 -0700
Parse command-line options and parse .ics file
src/Makefile.am | 2 +
src/application/california-application.vala | 50 ++++++++++-
src/application/california-commandline.vala | 67 ++++++++++++++
src/component/component-event.vala | 2 +-
src/component/component-icalendar.vala | 132 +++++++++++++++++++++++++++
src/component/component-instance.vala | 6 +-
vapi/libical.vapi | 14 ++--
7 files changed, 261 insertions(+), 12 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 074b53d..ae21505 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,7 @@ california_VALASOURCES = \
activator/webcal/activator-webcal-pane.vala \
\
application/california-application.vala \
+ application/california-commandline.vala \
application/california-resource.vala \
application/main.vala \
\
@@ -73,6 +74,7 @@ california_VALASOURCES = \
component/component-date-time.vala \
component/component-error.vala \
component/component-event.vala \
+ component/component-icalendar.vala \
component/component-instance.vala \
component/component-uid.vala \
component/component-vtype.vala \
diff --git a/src/application/california-application.vala b/src/application/california-application.vala
index 85e8efc..bcf82d6 100644
--- a/src/application/california-application.vala
+++ b/src/application/california-application.vala
@@ -16,11 +16,12 @@ namespace California {
public class Application : Gtk.Application {
public const string TITLE = _("California");
- public const string DESCRIPTION = _("Desktop Calendar");
+ public const string DESCRIPTION = _("GNOME 3 Calendar");
public const string COPYRIGHT = _("Copyright 2014 Yorba Foundation");
public const string VERSION = PACKAGE_VERSION;
public const string WEBSITE_NAME = _("Visit California's home page");
public const string WEBSITE_URL = "https://wiki.gnome.org/Apps/California";
+ public const string BUGREPORT_URL = "https://bugzilla.gnome.org/enter_bug.cgi?product=california";
public const string ID = "org.yorba.california";
public const string ICON_NAME = "x-office-calendar";
@@ -29,11 +30,16 @@ public class Application : Gtk.Application {
null
};
+ // public application menu actions; note their "app." prefix which does not
+ // match the actions in the action_entries table
public const string ACTION_NEW_CALENDAR = "app.new-calendar";
public const string ACTION_CALENDAR_MANAGER = "app.calendar-manager";
public const string ACTION_ABOUT = "app.about";
public const string ACTION_QUIT = "app.quit";
+ // internal actions; no "app." prefix
+ private const string ACTION_PROCESS_FILE = "process-file";
+
private static Application? _instance = null;
public static Application instance {
get {
@@ -42,10 +48,14 @@ public class Application : Gtk.Application {
}
private static const ActionEntry[] action_entries = {
+ // public actions
{ "new-calendar", on_new_calendar },
{ "calendar-manager", on_calendar_manager },
{ "about", on_about },
- { "quit", on_quit }
+ { "quit", on_quit },
+
+ // internal
+ { ACTION_PROCESS_FILE, on_process_file, "s" }
};
private Host.MainWindow? main_window = null;
@@ -59,6 +69,11 @@ public class Application : Gtk.Application {
public override bool local_command_line(ref unowned string[] args, out int exit_status) {
exec_file = File.new_for_path(Posix.realpath(Environment.find_program_in_path(args[0])));
+ // process arguments now, prior to register and activate; if true is returned before that,
+ // the application will exit with the exit code
+ if (!Commandline.parse(args, out exit_status))
+ return true;
+
try {
register();
} catch (Error err) {
@@ -67,6 +82,15 @@ public class Application : Gtk.Application {
activate();
+ // tell the primary instance (which this instance may not be) about the command-line options
+ // it should act upon
+ if (Commandline.files != null) {
+ foreach (string file in Commandline.files) {
+ debug("send %s", file);
+ activate_action(ACTION_PROCESS_FILE, file);
+ }
+ }
+
exit_status = 0;
return true;
@@ -139,6 +163,28 @@ public class Application : Gtk.Application {
Manager.Window.display(main_window);
}
+ private void on_process_file(SimpleAction action, Variant? variant) {
+ if (variant == null)
+ return;
+
+ // TODO: Support URIs
+ File file = File.new_for_commandline_arg((string) variant);
+ if (!file.is_native() || file.get_path() == null)
+ return;
+
+ Component.iCalendar ical;
+ try {
+ MappedFile mmap = new MappedFile(file.get_path(), false);
+ ical = Component.iCalendar.parse((string) mmap.get_contents());
+ } catch (Error err) {
+ message("Unable to add %s: %s", file.get_path(), err.message);
+
+ return;
+ }
+
+ debug("Loaded %s", ical.to_string());
+ }
+
private void on_about() {
Gtk.show_about_dialog(main_window,
"program-name", TITLE,
diff --git a/src/application/california-commandline.vala b/src/application/california-commandline.vala
new file mode 100644
index 0000000..324d5a4
--- /dev/null
+++ b/src/application/california-commandline.vala
@@ -0,0 +1,67 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+namespace California.Commandline {
+
+private const string PARAMS = _("[.ics FILE...]");
+
+public bool show_version = false;
+public Gee.List<string>? files = null;
+
+private const OptionEntry[] options = {
+ { "version", 'V', 0, OptionArg.NONE, ref show_version, N_("Display program version"), null },
+ { null }
+};
+
+/**
+ * Parse the command-line and process the obvious options, converting the remaining to options
+ * which are used by the remainder of the application.
+ *
+ * Returns false if the process should exit with the returned exit code.
+ */
+public bool parse(string[] args, out int exitcode) {
+ OptionContext context = new OptionContext(PARAMS);
+ context.set_help_enabled(true);
+ context.add_main_entries(options, null);
+ context.set_summary(Application.DESCRIPTION);
+ context.set_description("%s\n\n%s\n\t%s\n".printf(
+ Application.COPYRIGHT,
+ _("Please report problems and requests to:"),
+ Application.BUGREPORT_URL));
+
+ try {
+ context.parse(ref args);
+ } catch (OptionError opterr) {
+ stdout.printf(_("Unknown options: %s\n").printf(opterr.message));
+ stdout.printf("\n%s".printf(context.get_help(true, null)));
+
+ exitcode = 1;
+
+ return false;
+ }
+
+ // convert remaining arguments into files (although note that no sanity checking is
+ // performed)
+ for (int ctr = 1; ctr < args.length; ctr++) {
+ if (files == null)
+ files = new Gee.ArrayList<string>();
+
+ files.add(args[ctr]);
+ }
+
+ exitcode = 0;
+
+ if (show_version) {
+ stdout.printf("%s %s\n", Application.TITLE, Application.VERSION);
+
+ return false;
+ }
+
+ return true;
+}
+
+}
+
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 83930dc..af2ce4f 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -59,7 +59,7 @@ public class Event : Instance, Gee.Comparable<Event> {
*
* Throws a BackingError if the E.CalComponent's VTYPE is not VEVENT.
*/
- public Event(Backing.CalendarSource calendar_source, iCal.icalcomponent ical_component) throws Error {
+ public Event(Backing.CalendarSource? calendar_source, iCal.icalcomponent ical_component) throws Error {
base (calendar_source, ical_component, iCal.icalcomponent_kind.VEVENT_COMPONENT);
// remainder of state is initialized in update_from_component()
diff --git a/src/component/component-icalendar.vala b/src/component/component-icalendar.vala
new file mode 100644
index 0000000..e9e9b92
--- /dev/null
+++ b/src/component/component-icalendar.vala
@@ -0,0 +1,132 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+namespace California.Component {
+
+/**
+ * An immutable representation of an iCalendar VCALENDAR component.
+ *
+ * Note that a iCalendar is not considered an { link Instance}; it's a container which holds
+ * Instances. Although iCalendar is immutable, there is no guarantee that the Instances it
+ * hold will be.
+ *
+ * Also note that iCalendar currently is not associated with a { link Backing.CalendarSource}.
+ * If the feature is ever added where a CalendarSource can cough up its entire VCALENDAR,
+ * then that might make sense.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.6]].
+ */
+
+public class iCalendar : BaseObject {
+ /**
+ * The VCALENDAR's PRODID.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.7.3]]
+ */
+ public string? prodid { get; private set; default = null; }
+
+ /**
+ * The VCALENDAR's VERSION.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.7.4]. In particular,
+ * read the Purpose section.
+ */
+ public string? version { get; private set; default = null; }
+
+ /**
+ * The VCALENDAR's METHOD.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.7.2]]
+ */
+ public iCal.icalproperty_method method { get; private set; default = iCal.icalproperty_method.NONE; }
+
+ /**
+ * The VCALENDAR's CALSCALE.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.7.1]]
+ */
+ public string? calscale { get; private set; default = null; }
+
+ /**
+ * VEVENTS within the VCALENDAR.
+ */
+ public Gee.List<Event> events { get; private set; default = new Gee.ArrayList<Event>(); }
+
+ /**
+ * The iCal VCALENDAR this iCalendar represents.
+ */
+ private iCal.icalcomponent _ical_component;
+ public iCal.icalcomponent ical_component { get { return _ical_component; } }
+
+ /**
+ * Create an { link iCalendar} representation of the iCal component.
+ *
+ * @throws ComponentError.INVALID if root is not a VCALENDAR.
+ */
+ private iCalendar(owned iCal.icalcomponent root) throws Error {
+ if (root.isa() != iCal.icalcomponent_kind.VCALENDAR_COMPONENT)
+ throw new ComponentError.INVALID("Not a VCALENDAR");
+
+ //
+ // VCALENDAR properties
+ //
+
+ unowned iCal.icalproperty? prop = root.get_first_property(iCal.icalproperty_kind.PRODID_PROPERTY);
+ if (prop != null)
+ prodid = prop.get_prodid();
+
+ prop = root.get_first_property(iCal.icalproperty_kind.VERSION_PROPERTY);
+ if (prop != null)
+ version = prop.get_version();
+
+ prop = root.get_first_property(iCal.icalproperty_kind.CALSCALE_PROPERTY);
+ if (prop != null)
+ calscale = prop.get_calscale();
+
+ prop = root.get_first_property(iCal.icalproperty_kind.METHOD_PROPERTY);
+ if (prop != null)
+ method = prop.get_method();
+
+ //
+ // Contained components
+ //
+
+ // VEVENTS
+ unowned iCal.icalcomponent? child_component = root.get_first_component(
+ iCal.icalcomponent_kind.VEVENT_COMPONENT);
+ while (child_component != null) {
+ events.add(Instance.convert(null, child_component) as Event);
+ child_component = root.get_next_component(iCal.icalcomponent_kind.VEVENT_COMPONENT);
+ }
+
+ // take ownership
+ _ical_component = (owned) root;
+ }
+
+ /**
+ * Returns an appropriate { link Calendar} instance for the string of iCalendar data.
+ *
+ * @throws ComponentError if data is unrecognized
+ */
+ public static iCalendar parse(string? str) throws Error {
+ if (String.is_empty(str))
+ throw new ComponentError.INVALID("Empty VCALENDAR string");
+
+ iCal.icalcomponent? ical_component = iCal.icalparser.parse_string(str);
+ if (ical_component == null)
+ throw new ComponentError.INVALID("Unable to parse VCALENDAR (%db)".printf(str.length));
+
+ return new iCalendar((owned) ical_component);
+ }
+
+ public override string to_string() {
+ return "iCalendar|%s|%s|%s|%s (%d events)".printf(prodid, version, calscale, method.to_string(),
+ events.size);
+ }
+}
+
+}
+
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index f9769d7..037f851 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -115,7 +115,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
* external invocation of full_update() (such as immutable data) should update that state after
* the base constructor returns.
*/
- protected Instance(Backing.CalendarSource calendar_source, iCal.icalcomponent ical_component,
+ protected Instance(Backing.CalendarSource? calendar_source, iCal.icalcomponent ical_component,
iCal.icalcomponent_kind kind) throws Error {
if (ical_component.isa() != kind) {
throw new ComponentError.MISMATCH("Cannot create VTYPE %s from component of VTYPE %s",
@@ -260,9 +260,11 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
/**
* Returns an appropriate { link Component} instance for the iCalendar component.
*
+ * VCALENDARs should use { link Component.iCalendar}.
+ *
* @returns null if the component is not represented in this namespace (yet).
*/
- public static Component.Instance? convert(Backing.CalendarSource calendar_source,
+ public static Component.Instance? convert(Backing.CalendarSource? calendar_source,
iCal.icalcomponent ical_component) throws Error {
switch (ical_component.isa()) {
case iCal.icalcomponent_kind.VEVENT_COMPONENT:
diff --git a/vapi/libical.vapi b/vapi/libical.vapi
index 7950841..14ea598 100644
--- a/vapi/libical.vapi
+++ b/vapi/libical.vapi
@@ -85,9 +85,9 @@ namespace iCal {
[CCode (cname = "icalcomponent_get_comment")]
public unowned string get_comment ();
[CCode (cname = "icalcomponent_get_current_component")]
- public unowned iCal.icalcomponent get_current_component ();
+ public unowned iCal.icalcomponent? get_current_component ();
[CCode (cname = "icalcomponent_get_current_property")]
- public unowned iCal.icalproperty get_current_property ();
+ public unowned iCal.icalproperty? get_current_property ();
[CCode (cname = "icalcomponent_get_description")]
public unowned string get_description ();
[CCode (cname = "icalcomponent_get_dtend")]
@@ -101,7 +101,7 @@ namespace iCal {
[CCode (cname = "icalcomponent_get_duration")]
public unowned iCal.icaldurationtype get_duration ();
[CCode (cname = "icalcomponent_get_first_component")]
- public unowned iCal.icalcomponent get_first_component (iCal.icalcomponent_kind kind);
+ public unowned iCal.icalcomponent? get_first_component (iCal.icalcomponent_kind kind);
[CCode (cname = "icalcomponent_get_first_property")]
public unowned iCal.icalproperty? get_first_property (iCal.icalproperty_kind kind);
[CCode (cname = "icalcomponent_get_first_real_component")]
@@ -113,9 +113,9 @@ namespace iCal {
[CCode (cname = "icalcomponent_get_method")]
public iCal.icalproperty_method get_method ();
[CCode (cname = "icalcomponent_get_next_component")]
- public unowned iCal.icalcomponent get_next_component (iCal.icalcomponent_kind kind);
+ public unowned iCal.icalcomponent? get_next_component (iCal.icalcomponent_kind kind);
[CCode (cname = "icalcomponent_get_next_property")]
- public unowned iCal.icalproperty get_next_property (iCal.icalproperty_kind kind);
+ public unowned iCal.icalproperty? get_next_property (iCal.icalproperty_kind kind);
[CCode (cname = "icalcomponent_get_parent")]
public unowned iCal.icalcomponent get_parent ();
[CCode (cname = "icalcomponent_get_recurrenceid")]
@@ -481,9 +481,9 @@ namespace iCal {
[CCode (cname = "icalparser_get_state")]
public iCal.icalparser_state get_state ();
[CCode (cname = "icalparser_parse")]
- public unowned iCal.icalcomponent parse (GLib.Callback line_gen_func);
+ public iCal.icalcomponent? parse (GLib.Callback line_gen_func);
[CCode (cname = "icalparser_parse_string")]
- public static unowned iCal.icalcomponent parse_string (string str);
+ public static iCal.icalcomponent? parse_string (string str);
[CCode (cname = "icalparser_parse_value")]
public static unowned iCal.icalvalue parse_value (iCal.icalvalue_kind kind, string str, out
unowned iCal.icalcomponent errors);
[CCode (cname = "icalparser_set_gen_data")]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]