2015-10-29 13:53:03 -04:00
|
|
|
|
;;; ox-icalendar.el --- iCalendar Back-End for Org Export Engine -*- lexical-binding: t; -*-
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2019-01-01 05:50:56 -05:00
|
|
|
|
;; Copyright (C) 2004-2019 Free Software Foundation, Inc.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
;; Author: Carsten Dominik <carsten at orgmode dot org>
|
|
|
|
|
;; Nicolas Goaziou <n dot goaziou at gmail dot com>
|
|
|
|
|
;; Keywords: outlines, hypermedia, calendar, wp
|
2018-01-16 11:22:00 -05:00
|
|
|
|
;; Homepage: https://orgmode.org
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
Backport changes from Emacs revs 115081 and 115082
2013-11-12 Stefan Monnier <monnier@iro.umontreal.ca>
Address some byte-compiler warnings.
* ob-abc.el (org-babel-expand-body:abc): Use dolist.
(org-babel-execute:abc): Fix regexp quoting.
* ob-calc.el (org--var-syms): Rename from `var-syms'.
* ob-lilypond.el (ly-compile-lilyfile): Remove redundant let-binding.
* ob-table.el (sbe): Move debug declaration.
* org-clock.el (org--msg-extra): Rename from `msg-extra'.
* org.el (org-version): Avoid var name starting with _.
(org-inhibit-startup, org-called-with-limited-levels)
(org-link-search-inhibit-query, org-time-was-given)
(org-end-time-was-given, org-def, org-defdecode, org-with-time):
* org-colview.el (org-agenda-overriding-columns-format):
* org-agenda.el (org-agenda-multi, org-depend-tag-blocked)
(org-agenda-show-log-scoped):
* ob-python.el (py-which-bufname, python-shell-buffer-name):
* ob-haskell.el (org-export-copy-to-kill-ring):
* ob-exp.el (org-link-search-inhibit-query):
* ob-R.el (ess-eval-visibly-p):
* ob-core.el (org-src-window-setup): Declare before use.
(org-babel-expand-noweb-references): Remove unused `blocks-in-buffer'.
* ox-odt.el (org-odt-hfy-face-to-css):
* org-src.el (org-src-associate-babel-session, org-src-get-lang-mode):
* org-bibtex.el (org-bibtex-get, org-bibtex-ask, org-bibtex)
(org-bibtex-check):
* ob-tangle.el (org-babel-tangle, org-babel-spec-to-string)
(org-babel-tangle-single-block, org-babel-tangle-comment-links):
* ob-table.el (sbe):
* ob-sqlite.el (org-babel-sqlite-expand-vars):
* ob-sql.el (org-babel-sql-expand-vars):
* ob-shen.el (org-babel-execute:shen):
* ob-sh.el (org-babel-execute:sh, org-babel-sh-evaluate):
* ob-scala.el (org-babel-scala-evaluate):
* ob-ruby.el (org-babel-ruby-table-or-string)
(org-babel-ruby-evaluate):
* ob-python.el (org-babel-python-table-or-string)
(org-babel-python-evaluate-external-process)
(org-babel-python-evaluate-session):
* ob-picolisp.el (org-babel-execute:picolisp):
* ob-perl.el (org-babel-perl-evaluate):
* ob-maxima.el (org-babel-execute:maxima):
* ob-lisp.el (org-babel-execute:lisp):
* ob-java.el (org-babel-execute:java):
* ob-io.el (org-babel-io-evaluate):
* ob-haskell.el (org-babel-execute:haskell):
* ob-fortran.el (org-babel-execute:fortran):
* ob-exp.el (org-babel-exp-code):
* ob-emacs-lisp.el (org-babel-execute:emacs-lisp):
* ob-ditaa.el (org-babel-execute:ditaa):
* ob-core.el (org-babel-execute-src-block, org-babel-sha1-hash)
(org-babel-parse-header-arguments, org-babel-reassemble-table)
(org-babel-goto-src-block-head, org-babel-mark-block)
(org-babel-expand-noweb-references, org-babel-script-escape)
(org-babel-process-file-name):
* ob-clojure.el (org-babel-execute:clojure):
* ob-calc.el (org-babel-execute:calc):
* ob-awk.el (org-babel-execute:awk):
* ob-abc.el (org-babel-execute:abc):
* ob-R.el (org-babel-expand-body:R):
* ob-C.el (org-babel-C-execute): Avoid deprecated ((lambda) ...).
2013-11-12 Glenn Morris <rgm@gnu.org>
* ox-html.el (org-html-scripts): Add 2013 to copyright years.
(org-html-infojs-template): Copyright holder to FSF.
2013-11-12 14:57:31 -05:00
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
|
|
2013-02-23 03:56:24 -05:00
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
2013-02-23 03:56:24 -05:00
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
2017-09-13 18:52:52 -04:00
|
|
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
;;
|
|
|
|
|
;; This library implements an iCalendar back-end for Org generic
|
2013-10-20 04:58:30 -04:00
|
|
|
|
;; exporter. See Org manual for more information.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;;
|
2013-10-20 04:58:30 -04:00
|
|
|
|
;; It is expected to conform to RFC 5545.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2015-11-06 07:04:20 -05:00
|
|
|
|
(require 'cl-lib)
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(require 'ox-ascii)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(declare-function org-bbdb-anniv-export-ical "org-bbdb" nil)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; User-Configurable Variables
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defgroup org-export-icalendar nil
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Options specific for iCalendar export back-end."
|
2013-02-15 16:01:09 -05:00
|
|
|
|
:tag "Org Export iCalendar"
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:group 'org-export)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-combined-agenda-file "~/org.ics"
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"The file name for the iCalendar file covering all agenda files.
|
2016-10-15 11:36:47 -04:00
|
|
|
|
This file is created with the command `\\[org-icalendar-combine-agenda-files]'.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
The file name should be absolute. It will be overwritten without warning."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type 'file)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-alarm-time 0
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Number of minutes for triggering an alarm for exported timed events.
|
|
|
|
|
|
|
|
|
|
A zero value (the default) turns off the definition of an alarm trigger
|
|
|
|
|
for timed events. If non-zero, alarms are created.
|
|
|
|
|
|
|
|
|
|
- a single alarm per entry is defined
|
|
|
|
|
- The alarm will go off N minutes before the event
|
|
|
|
|
- only a DISPLAY action is defined."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:version "24.1"
|
|
|
|
|
:type 'integer)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-combined-name "OrgMode"
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Calendar name for the combined iCalendar representing all agenda files."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type 'string)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-combined-description ""
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Calendar description for the combined iCalendar (all agenda files)."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type 'string)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-exclude-tags nil
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Tags that exclude a tree from export.
|
2016-01-24 15:30:39 -05:00
|
|
|
|
This variable allows specifying different exclude tags from other
|
2015-10-01 07:51:13 -04:00
|
|
|
|
back-ends. It can also be set with the ICALENDAR_EXCLUDE_TAGS
|
2012-09-14 09:29:39 -04:00
|
|
|
|
keyword."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type '(repeat (string :tag "Tag")))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-use-deadline '(event-if-not-todo todo-due)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Contexts where iCalendar export should use a deadline time stamp.
|
|
|
|
|
|
2014-04-23 04:06:43 -04:00
|
|
|
|
This is a list with possibly several symbols in it. Valid symbols are:
|
|
|
|
|
|
2012-09-14 09:29:39 -04:00
|
|
|
|
`event-if-todo' Deadlines in TODO entries become calendar events.
|
|
|
|
|
`event-if-not-todo' Deadlines in non-TODO entries become calendar events.
|
2014-04-23 04:06:43 -04:00
|
|
|
|
`todo-due' Use deadlines in TODO entries as due-dates."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type '(set :greedy t
|
|
|
|
|
(const :tag "Deadlines in non-TODO entries become events"
|
|
|
|
|
event-if-not-todo)
|
|
|
|
|
(const :tag "Deadline in TODO entries become events"
|
|
|
|
|
event-if-todo)
|
|
|
|
|
(const :tag "Deadlines in TODO entries become due-dates"
|
|
|
|
|
todo-due)))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-use-scheduled '(todo-start)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Contexts where iCalendar export should use a scheduling time stamp.
|
|
|
|
|
|
2014-04-23 03:50:25 -04:00
|
|
|
|
This is a list with possibly several symbols in it. Valid symbols are:
|
|
|
|
|
|
2012-09-14 09:29:39 -04:00
|
|
|
|
`event-if-todo' Scheduling time stamps in TODO entries become an event.
|
|
|
|
|
`event-if-not-todo' Scheduling time stamps in non-TODO entries become an event.
|
|
|
|
|
`todo-start' Scheduling time stamps in TODO entries become start date.
|
|
|
|
|
Some calendar applications show TODO entries only after
|
|
|
|
|
that date."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type '(set :greedy t
|
|
|
|
|
(const :tag
|
|
|
|
|
"SCHEDULED timestamps in non-TODO entries become events"
|
|
|
|
|
event-if-not-todo)
|
|
|
|
|
(const :tag "SCHEDULED timestamps in TODO entries become events"
|
|
|
|
|
event-if-todo)
|
|
|
|
|
(const :tag "SCHEDULED in TODO entries become start date"
|
|
|
|
|
todo-start)))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-categories '(local-tags category)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Items that should be entered into the \"categories\" field.
|
|
|
|
|
|
|
|
|
|
This is a list of symbols, the following are valid:
|
|
|
|
|
`category' The Org mode category of the current file or tree
|
|
|
|
|
`todo-state' The todo state, if any
|
|
|
|
|
`local-tags' The tags, defined in the current line
|
|
|
|
|
`all-tags' All tags, including inherited ones."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type '(repeat
|
|
|
|
|
(choice
|
|
|
|
|
(const :tag "The file or tree category" category)
|
|
|
|
|
(const :tag "The TODO state" todo-state)
|
|
|
|
|
(const :tag "Tags defined in current line" local-tags)
|
|
|
|
|
(const :tag "All tags, including inherited ones" all-tags))))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-with-timestamps 'active
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Non-nil means make an event from plain time stamps.
|
|
|
|
|
|
|
|
|
|
It can be set to `active', `inactive', t or nil, in order to make
|
|
|
|
|
an event from, respectively, only active timestamps, only
|
|
|
|
|
inactive ones, all of them or none.
|
|
|
|
|
|
|
|
|
|
This variable has precedence over `org-export-with-timestamps'.
|
|
|
|
|
It can also be set with the #+OPTIONS line, e.g. \"<:t\"."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type '(choice
|
|
|
|
|
(const :tag "All timestamps" t)
|
|
|
|
|
(const :tag "Only active timestamps" active)
|
|
|
|
|
(const :tag "Only inactive timestamps" inactive)
|
|
|
|
|
(const :tag "No timestamp" nil)))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-include-todo nil
|
2012-11-05 08:02:07 -05:00
|
|
|
|
"Non-nil means create VTODO components from TODO items.
|
|
|
|
|
|
|
|
|
|
Valid values are:
|
|
|
|
|
nil don't include any task.
|
|
|
|
|
t include tasks that are not in DONE state.
|
|
|
|
|
`unblocked' include all TODO items that are not blocked.
|
|
|
|
|
`all' include both done and not done items."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-11-05 08:02:07 -05:00
|
|
|
|
:type '(choice
|
|
|
|
|
(const :tag "None" nil)
|
|
|
|
|
(const :tag "Unfinished" t)
|
|
|
|
|
(const :tag "Unblocked" unblocked)
|
|
|
|
|
(const :tag "All" all)
|
|
|
|
|
(repeat :tag "Specific TODO keywords"
|
|
|
|
|
(string :tag "Keyword"))))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-include-bbdb-anniversaries nil
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Non-nil means a combined iCalendar file should include anniversaries.
|
|
|
|
|
The anniversaries are defined in the BBDB database."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-include-sexps t
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Non-nil means export to iCalendar files should also cover sexp entries.
|
2014-04-23 15:53:58 -04:00
|
|
|
|
These are entries like in the diary, but directly in an Org file."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-include-body t
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Amount of text below headline to be included in iCalendar export.
|
|
|
|
|
This is a number of characters that should maximally be included.
|
|
|
|
|
Properties, scheduling and clocking lines will always be removed.
|
|
|
|
|
The text will be inserted into the DESCRIPTION field."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type '(choice
|
|
|
|
|
(const :tag "Nothing" nil)
|
|
|
|
|
(const :tag "Everything" t)
|
|
|
|
|
(integer :tag "Max characters")))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-store-UID nil
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Non-nil means store any created UIDs in properties.
|
|
|
|
|
|
|
|
|
|
The iCalendar standard requires that all entries have a unique identifier.
|
|
|
|
|
Org will create these identifiers as needed. When this variable is non-nil,
|
|
|
|
|
the created UIDs will be stored in the ID property of the entry. Then the
|
|
|
|
|
next time this entry is exported, it will be exported with the same UID,
|
|
|
|
|
superseding the previous form of it. This is essential for
|
|
|
|
|
synchronization services.
|
|
|
|
|
|
|
|
|
|
This variable is not turned on by default because we want to avoid creating
|
|
|
|
|
a property drawer in every entry if people are only playing with this feature,
|
|
|
|
|
or if they are only using it locally."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-timezone (getenv "TZ")
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"The time zone string for iCalendar export.
|
|
|
|
|
When nil or the empty string, use output
|
|
|
|
|
from (current-time-zone)."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:type '(choice
|
|
|
|
|
(const :tag "Unspecified" nil)
|
|
|
|
|
(string :tag "Time zone")))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defcustom org-icalendar-date-time-format ":%Y%m%dT%H%M%S"
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Format-string for exporting icalendar DATE-TIME.
|
|
|
|
|
|
|
|
|
|
See `format-time-string' for a full documentation. The only
|
2013-01-27 17:11:34 -05:00
|
|
|
|
difference is that `org-icalendar-timezone' is used for %Z.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
Interesting value are:
|
|
|
|
|
- \":%Y%m%dT%H%M%S\" for local time
|
|
|
|
|
- \";TZID=%Z:%Y%m%dT%H%M%S\" for local time with explicit timezone
|
|
|
|
|
- \":%Y%m%dT%H%M%SZ\" for time expressed in Universal Time"
|
2013-01-27 17:11:34 -05:00
|
|
|
|
:group 'org-export-icalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:version "24.1"
|
|
|
|
|
:type '(choice
|
|
|
|
|
(const :tag "Local time" ":%Y%m%dT%H%M%S")
|
|
|
|
|
(const :tag "Explicit local time" ";TZID=%Z:%Y%m%dT%H%M%S")
|
|
|
|
|
(const :tag "Universal time" ":%Y%m%dT%H%M%SZ")
|
|
|
|
|
(string :tag "Explicit format")))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defvar org-icalendar-after-save-hook nil
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Hook run after an iCalendar file has been saved.
|
|
|
|
|
This hook is run with the name of the file as argument. A good
|
|
|
|
|
way to use this is to tell a desktop calendar application to
|
|
|
|
|
re-read the iCalendar file.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Define Back-End
|
|
|
|
|
|
2013-03-19 11:24:40 -04:00
|
|
|
|
(org-export-define-derived-backend 'icalendar 'ascii
|
|
|
|
|
:translate-alist '((clock . ignore)
|
2013-04-21 17:41:49 -04:00
|
|
|
|
(footnote-definition . ignore)
|
|
|
|
|
(footnote-reference . ignore)
|
2013-03-19 11:24:40 -04:00
|
|
|
|
(headline . org-icalendar-entry)
|
|
|
|
|
(inlinetask . ignore)
|
|
|
|
|
(planning . ignore)
|
|
|
|
|
(section . ignore)
|
2013-04-21 17:41:49 -04:00
|
|
|
|
(inner-template . (lambda (c i) c))
|
2013-03-19 11:24:40 -04:00
|
|
|
|
(template . org-icalendar-template))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:options-alist
|
2013-03-19 11:24:40 -04:00
|
|
|
|
'((:exclude-tags
|
|
|
|
|
"ICALENDAR_EXCLUDE_TAGS" nil org-icalendar-exclude-tags split)
|
|
|
|
|
(:with-timestamps nil "<" org-icalendar-with-timestamps)
|
2014-05-21 03:06:59 -04:00
|
|
|
|
;; Other variables.
|
|
|
|
|
(:icalendar-alarm-time nil nil org-icalendar-alarm-time)
|
|
|
|
|
(:icalendar-categories nil nil org-icalendar-categories)
|
|
|
|
|
(:icalendar-date-time-format nil nil org-icalendar-date-time-format)
|
|
|
|
|
(:icalendar-include-bbdb-anniversaries nil nil org-icalendar-include-bbdb-anniversaries)
|
|
|
|
|
(:icalendar-include-body nil nil org-icalendar-include-body)
|
|
|
|
|
(:icalendar-include-sexps nil nil org-icalendar-include-sexps)
|
|
|
|
|
(:icalendar-include-todo nil nil org-icalendar-include-todo)
|
|
|
|
|
(:icalendar-store-UID nil nil org-icalendar-store-UID)
|
|
|
|
|
(:icalendar-timezone nil nil org-icalendar-timezone)
|
|
|
|
|
(:icalendar-use-deadline nil nil org-icalendar-use-deadline)
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(:icalendar-use-scheduled nil nil org-icalendar-use-scheduled))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:filters-alist
|
2013-03-19 11:24:40 -04:00
|
|
|
|
'((:filter-headline . org-icalendar-clear-blank-lines))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
:menu-entry
|
2013-03-19 11:24:40 -04:00
|
|
|
|
'(?c "Export to iCalendar"
|
|
|
|
|
((?f "Current file" org-icalendar-export-to-ics)
|
|
|
|
|
(?a "All agenda files"
|
|
|
|
|
(lambda (a s v b) (org-icalendar-export-agenda-files a)))
|
|
|
|
|
(?c "Combine all agenda files"
|
|
|
|
|
(lambda (a s v b) (org-icalendar-combine-agenda-files a))))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Internal Functions
|
|
|
|
|
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(defun org-icalendar-create-uid (file &optional bell)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Set ID property on headlines missing it in FILE.
|
|
|
|
|
When optional argument BELL is non-nil, inform the user with
|
2014-08-25 18:08:38 -04:00
|
|
|
|
a message if the file was modified."
|
|
|
|
|
(let (modified-flag)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(org-map-entries
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((entry (org-element-at-point)))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(unless (org-element-property :ID entry)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(org-id-get-create)
|
|
|
|
|
(setq modified-flag t)
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(forward-line))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
nil nil 'comment)
|
|
|
|
|
(when (and bell modified-flag)
|
|
|
|
|
(message "ID properties created in file \"%s\"" file)
|
|
|
|
|
(sit-for 2))))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-blocked-headline-p (headline info)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Non-nil when HEADLINE is considered to be blocked.
|
|
|
|
|
|
|
|
|
|
INFO is a plist used as a communication channel.
|
|
|
|
|
|
2014-04-23 15:53:58 -04:00
|
|
|
|
A headline is blocked when either
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2014-04-23 15:53:58 -04:00
|
|
|
|
- it has children which are not all in a completed state;
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2014-04-23 15:53:58 -04:00
|
|
|
|
- it has a parent with the property :ORDERED:, and there are
|
|
|
|
|
siblings prior to it with incomplete status;
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2014-04-23 15:53:58 -04:00
|
|
|
|
- its parent is blocked because it has siblings that should be
|
2012-09-14 09:29:39 -04:00
|
|
|
|
done first or is a child of a blocked grandparent entry."
|
|
|
|
|
(or
|
|
|
|
|
;; Check if any child is not done.
|
2019-02-21 09:33:00 -05:00
|
|
|
|
(org-element-map (org-element-contents headline) 'headline
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(lambda (hl) (eq (org-element-property :todo-type hl) 'todo))
|
|
|
|
|
info 'first-match)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; Check :ORDERED: node property.
|
|
|
|
|
(catch 'blockedp
|
|
|
|
|
(let ((current headline))
|
2014-11-16 07:27:34 -05:00
|
|
|
|
(dolist (parent (org-element-lineage headline))
|
|
|
|
|
(cond
|
|
|
|
|
((not (org-element-property :todo-keyword parent))
|
|
|
|
|
(throw 'blockedp nil))
|
|
|
|
|
((org-not-nil (org-element-property :ORDERED parent))
|
|
|
|
|
(let ((sibling current))
|
|
|
|
|
(while (setq sibling (org-export-get-previous-element
|
|
|
|
|
sibling info))
|
|
|
|
|
(when (eq (org-element-property :todo-type sibling) 'todo)
|
|
|
|
|
(throw 'blockedp t)))))
|
|
|
|
|
(t (setq current parent))))))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-use-UTC-date-time-p ()
|
|
|
|
|
"Non-nil when `org-icalendar-date-time-format' requires UTC time."
|
|
|
|
|
(char-equal (elt org-icalendar-date-time-format
|
|
|
|
|
(1- (length org-icalendar-date-time-format))) ?Z))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
(defvar org-agenda-default-appointment-duration) ; From org-agenda.el.
|
2017-08-19 18:11:18 -04:00
|
|
|
|
(defun org-icalendar-convert-timestamp (timestamp keyword &optional end tz)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Convert TIMESTAMP to iCalendar format.
|
|
|
|
|
|
|
|
|
|
TIMESTAMP is a timestamp object. KEYWORD is added in front of
|
|
|
|
|
it, in order to make a complete line (e.g. \"DTSTART\").
|
|
|
|
|
|
|
|
|
|
When optional argument END is non-nil, use end of time range.
|
|
|
|
|
Also increase the hour by two (if time string contains a time),
|
2012-11-05 09:24:09 -05:00
|
|
|
|
or the day by one (if it does not contain a time) when no
|
|
|
|
|
explicit ending time is specified.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2017-08-19 18:11:18 -04:00
|
|
|
|
When optional argument TZ is non-nil, timezone data time will be
|
|
|
|
|
added to the timestamp. It can be the string \"UTC\", to use UTC
|
|
|
|
|
time, or a string in the IANA TZ database
|
|
|
|
|
format (e.g. \"Europe/London\"). In either case, the value of
|
|
|
|
|
`org-icalendar-date-time-format' will be ignored."
|
2012-11-05 09:24:09 -05:00
|
|
|
|
(let* ((year-start (org-element-property :year-start timestamp))
|
|
|
|
|
(year-end (org-element-property :year-end timestamp))
|
|
|
|
|
(month-start (org-element-property :month-start timestamp))
|
|
|
|
|
(month-end (org-element-property :month-end timestamp))
|
|
|
|
|
(day-start (org-element-property :day-start timestamp))
|
|
|
|
|
(day-end (org-element-property :day-end timestamp))
|
|
|
|
|
(hour-start (org-element-property :hour-start timestamp))
|
|
|
|
|
(hour-end (org-element-property :hour-end timestamp))
|
|
|
|
|
(minute-start (org-element-property :minute-start timestamp))
|
|
|
|
|
(minute-end (org-element-property :minute-end timestamp))
|
|
|
|
|
(with-time-p minute-start)
|
|
|
|
|
(equal-bounds-p
|
|
|
|
|
(equal (list year-start month-start day-start hour-start minute-start)
|
|
|
|
|
(list year-end month-end day-end hour-end minute-end)))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(mi (cond ((not with-time-p) 0)
|
2012-11-05 09:24:09 -05:00
|
|
|
|
((not end) minute-start)
|
|
|
|
|
((and org-agenda-default-appointment-duration equal-bounds-p)
|
|
|
|
|
(+ minute-end org-agenda-default-appointment-duration))
|
|
|
|
|
(t minute-end)))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(h (cond ((not with-time-p) 0)
|
2012-11-05 09:24:09 -05:00
|
|
|
|
((not end) hour-start)
|
|
|
|
|
((or (not equal-bounds-p)
|
|
|
|
|
org-agenda-default-appointment-duration)
|
|
|
|
|
hour-end)
|
|
|
|
|
(t (+ hour-end 2))))
|
|
|
|
|
(d (cond ((not end) day-start)
|
|
|
|
|
((not with-time-p) (1+ day-end))
|
|
|
|
|
(t day-end)))
|
|
|
|
|
(m (if end month-end month-start))
|
|
|
|
|
(y (if end year-end year-start)))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(concat
|
|
|
|
|
keyword
|
|
|
|
|
(format-time-string
|
2017-08-19 18:11:18 -04:00
|
|
|
|
(cond ((string-equal tz "UTC") ":%Y%m%dT%H%M%SZ")
|
2012-09-14 09:29:39 -04:00
|
|
|
|
((not with-time-p) ";VALUE=DATE:%Y%m%d")
|
2017-08-19 18:11:18 -04:00
|
|
|
|
((stringp tz) (concat ";TZID=" tz ":%Y%m%dT%H%M%S"))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(t (replace-regexp-in-string "%Z"
|
2013-01-27 17:11:34 -05:00
|
|
|
|
org-icalendar-timezone
|
|
|
|
|
org-icalendar-date-time-format
|
2013-01-08 04:37:56 -05:00
|
|
|
|
t)))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; Convert timestamp into internal time in order to use
|
|
|
|
|
;; `format-time-string' and fix any mistake (i.e. MI >= 60).
|
|
|
|
|
(encode-time 0 mi h d m y)
|
2017-08-19 18:11:18 -04:00
|
|
|
|
(and (or (string-equal tz "UTC")
|
|
|
|
|
(and (null tz)
|
|
|
|
|
with-time-p
|
|
|
|
|
(org-icalendar-use-UTC-date-time-p)))
|
2016-04-11 00:32:08 -04:00
|
|
|
|
t)))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2013-04-05 15:28:35 -04:00
|
|
|
|
(defun org-icalendar-dtstamp ()
|
|
|
|
|
"Return DTSTAMP property, as a string."
|
|
|
|
|
(format-time-string "DTSTAMP:%Y%m%dT%H%M%SZ" nil t))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-get-categories (entry info)
|
|
|
|
|
"Return categories according to `org-icalendar-categories'.
|
2013-02-23 07:47:44 -05:00
|
|
|
|
ENTRY is a headline or an inlinetask element. INFO is a plist
|
2012-09-14 09:29:39 -04:00
|
|
|
|
used as a communication channel."
|
|
|
|
|
(mapconcat
|
2015-10-29 13:53:03 -04:00
|
|
|
|
#'identity
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(org-uniquify
|
|
|
|
|
(let (categories)
|
2015-10-29 13:53:03 -04:00
|
|
|
|
(dolist (type org-icalendar-categories (nreverse categories))
|
2016-05-26 10:08:15 -04:00
|
|
|
|
(cl-case type
|
2015-10-29 13:53:03 -04:00
|
|
|
|
(category
|
|
|
|
|
(push (org-export-get-category entry info) categories))
|
|
|
|
|
(todo-state
|
|
|
|
|
(let ((todo (org-element-property :todo-keyword entry)))
|
|
|
|
|
(and todo (push todo categories))))
|
|
|
|
|
(local-tags
|
|
|
|
|
(setq categories
|
|
|
|
|
(append (nreverse (org-export-get-tags entry info))
|
|
|
|
|
categories)))
|
|
|
|
|
(all-tags
|
|
|
|
|
(setq categories
|
|
|
|
|
(append (nreverse (org-export-get-tags entry info nil t))
|
|
|
|
|
categories)))))))
|
|
|
|
|
","))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-transcode-diary-sexp (sexp uid summary)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Transcode a diary sexp into iCalendar format.
|
|
|
|
|
SEXP is the diary sexp being transcoded, as a string. UID is the
|
|
|
|
|
unique identifier for the entry. SUMMARY defines a short summary
|
|
|
|
|
or subject for the event."
|
|
|
|
|
(when (require 'icalendar nil t)
|
|
|
|
|
(org-element-normalize-string
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(let ((sexp (if (not (string-match "\\`<%%" sexp)) sexp
|
|
|
|
|
(concat (substring sexp 1 -1) " " summary))))
|
|
|
|
|
(put-text-property 0 1 'uid uid sexp)
|
|
|
|
|
(insert sexp "\n"))
|
|
|
|
|
(org-diary-to-ical-string (current-buffer))))))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-cleanup-string (s)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Cleanup string S according to RFC 5545."
|
|
|
|
|
(when s
|
|
|
|
|
;; Protect "\", "," and ";" characters. and replace newline
|
|
|
|
|
;; characters with literal \n.
|
|
|
|
|
(replace-regexp-in-string
|
|
|
|
|
"[ \t]*\n" "\\n"
|
2015-09-17 15:28:45 -04:00
|
|
|
|
(replace-regexp-in-string "[\\,;]" "\\\\\\&" s)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
nil t)))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-fold-string (s)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Fold string S according to RFC 5545."
|
|
|
|
|
(org-element-normalize-string
|
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (line)
|
|
|
|
|
;; Limit each line to a maximum of 75 characters. If it is
|
2016-11-18 17:03:01 -05:00
|
|
|
|
;; longer, fold it by using "\r\n " as a continuation marker.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(let ((len (length line)))
|
|
|
|
|
(if (<= len 75) line
|
|
|
|
|
(let ((folded-line (substring line 0 75))
|
|
|
|
|
(chunk-start 75)
|
|
|
|
|
chunk-end)
|
|
|
|
|
;; Since continuation marker takes up one character on the
|
|
|
|
|
;; line, real contents must be split at 74 chars.
|
|
|
|
|
(while (< (setq chunk-end (+ chunk-start 74)) len)
|
|
|
|
|
(setq folded-line
|
2016-11-18 17:03:01 -05:00
|
|
|
|
(concat folded-line "\r\n "
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(substring line chunk-start chunk-end))
|
|
|
|
|
chunk-start chunk-end))
|
2016-11-18 17:03:01 -05:00
|
|
|
|
(concat folded-line "\r\n " (substring line chunk-start))))))
|
|
|
|
|
(org-split-string s "\n") "\r\n")))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Filters
|
|
|
|
|
|
2015-10-29 13:53:03 -04:00
|
|
|
|
(defun org-icalendar-clear-blank-lines (headline _back-end _info)
|
2014-04-23 15:53:58 -04:00
|
|
|
|
"Remove blank lines in HEADLINE export.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
HEADLINE is a string representing a transcoded headline.
|
|
|
|
|
BACK-END and INFO are ignored."
|
2014-04-23 15:53:58 -04:00
|
|
|
|
(replace-regexp-in-string "^\\(?:[ \t]*\n\\)+" "" headline))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Transcode Functions
|
|
|
|
|
|
|
|
|
|
;;;; Headline and Inlinetasks
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
;; The main function is `org-icalendar-entry', which extracts
|
2013-02-23 07:47:44 -05:00
|
|
|
|
;; information from a headline or an inlinetask (summary,
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; description...) and then delegates code generation to
|
2013-01-27 17:11:34 -05:00
|
|
|
|
;; `org-icalendar--vtodo' and `org-icalendar--vevent', depending
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; on the component needed.
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
;; Obviously, `org-icalendar--valarm' handles alarms, which can
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; happen within a VTODO component.
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-entry (entry contents info)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Transcode ENTRY element into iCalendar format.
|
|
|
|
|
|
2013-02-23 07:47:44 -05:00
|
|
|
|
ENTRY is either a headline or an inlinetask. CONTENTS is
|
2012-09-14 09:29:39 -04:00
|
|
|
|
ignored. INFO is a plist used as a communication channel.
|
|
|
|
|
|
|
|
|
|
This function is called on every headline, the section below
|
|
|
|
|
it (minus inlinetasks) being its contents. It tries to create
|
|
|
|
|
VEVENT and VTODO components out of scheduled date, deadline date,
|
|
|
|
|
plain timestamps, diary sexps. It also calls itself on every
|
|
|
|
|
inlinetask within the section."
|
|
|
|
|
(unless (org-element-property :footnote-section-p entry)
|
|
|
|
|
(let* ((type (org-element-type entry))
|
|
|
|
|
;; Determine contents really associated to the entry. For
|
2013-02-23 07:47:44 -05:00
|
|
|
|
;; a headline, limit them to section, if any. For an
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; inlinetask, this is every element within the task.
|
|
|
|
|
(inside
|
|
|
|
|
(if (eq type 'inlinetask)
|
|
|
|
|
(cons 'org-data (cons nil (org-element-contents entry)))
|
|
|
|
|
(let ((first (car (org-element-contents entry))))
|
|
|
|
|
(and (eq (org-element-type first) 'section)
|
|
|
|
|
(cons 'org-data
|
|
|
|
|
(cons nil (org-element-contents first))))))))
|
|
|
|
|
(concat
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(let ((todo-type (org-element-property :todo-type entry))
|
|
|
|
|
(uid (or (org-element-property :ID entry) (org-id-new)))
|
|
|
|
|
(summary (org-icalendar-cleanup-string
|
|
|
|
|
(or (org-element-property :SUMMARY entry)
|
|
|
|
|
(org-export-data
|
|
|
|
|
(org-element-property :title entry) info))))
|
|
|
|
|
(loc (org-icalendar-cleanup-string
|
2017-08-19 18:12:57 -04:00
|
|
|
|
(org-export-get-node-property
|
|
|
|
|
:LOCATION entry
|
|
|
|
|
(org-property-inherit-p "LOCATION"))))
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(class (org-icalendar-cleanup-string
|
|
|
|
|
(org-export-get-node-property
|
|
|
|
|
:CLASS entry
|
|
|
|
|
(org-property-inherit-p "CLASS"))))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
;; Build description of the entry from associated section
|
|
|
|
|
;; (headline) or contents (inlinetask).
|
|
|
|
|
(desc
|
|
|
|
|
(org-icalendar-cleanup-string
|
|
|
|
|
(or (org-element-property :DESCRIPTION entry)
|
|
|
|
|
(let ((contents (org-export-data inside info)))
|
|
|
|
|
(cond
|
|
|
|
|
((not (org-string-nw-p contents)) nil)
|
|
|
|
|
((wholenump org-icalendar-include-body)
|
|
|
|
|
(let ((contents (org-trim contents)))
|
|
|
|
|
(substring
|
|
|
|
|
contents 0 (min (length contents)
|
|
|
|
|
org-icalendar-include-body))))
|
|
|
|
|
(org-icalendar-include-body (org-trim contents)))))))
|
2017-08-19 18:11:18 -04:00
|
|
|
|
(cat (org-icalendar-get-categories entry info))
|
2017-08-19 18:12:57 -04:00
|
|
|
|
(tz (org-export-get-node-property
|
|
|
|
|
:TIMEZONE entry
|
|
|
|
|
(org-property-inherit-p "TIMEZONE"))))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(concat
|
|
|
|
|
;; Events: Delegate to `org-icalendar--vevent' to generate
|
|
|
|
|
;; "VEVENT" component from scheduled, deadline, or any
|
|
|
|
|
;; timestamp in the entry.
|
|
|
|
|
(let ((deadline (org-element-property :deadline entry)))
|
|
|
|
|
(and deadline
|
|
|
|
|
(memq (if todo-type 'event-if-todo 'event-if-not-todo)
|
|
|
|
|
org-icalendar-use-deadline)
|
|
|
|
|
(org-icalendar--vevent
|
|
|
|
|
entry deadline (concat "DL-" uid)
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(concat "DL: " summary) loc desc cat tz class)))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(let ((scheduled (org-element-property :scheduled entry)))
|
|
|
|
|
(and scheduled
|
|
|
|
|
(memq (if todo-type 'event-if-todo 'event-if-not-todo)
|
|
|
|
|
org-icalendar-use-scheduled)
|
|
|
|
|
(org-icalendar--vevent
|
|
|
|
|
entry scheduled (concat "SC-" uid)
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(concat "S: " summary) loc desc cat tz class)))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
;; When collecting plain timestamps from a headline and its
|
|
|
|
|
;; title, skip inlinetasks since collection will happen once
|
|
|
|
|
;; ENTRY is one of them.
|
|
|
|
|
(let ((counter 0))
|
|
|
|
|
(mapconcat
|
|
|
|
|
#'identity
|
|
|
|
|
(org-element-map (cons (org-element-property :title entry)
|
|
|
|
|
(org-element-contents inside))
|
|
|
|
|
'timestamp
|
|
|
|
|
(lambda (ts)
|
|
|
|
|
(when (let ((type (org-element-property :type ts)))
|
2016-05-26 10:08:15 -04:00
|
|
|
|
(cl-case (plist-get info :with-timestamps)
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(active (memq type '(active active-range)))
|
|
|
|
|
(inactive (memq type '(inactive inactive-range)))
|
|
|
|
|
((t) t)))
|
2016-05-26 10:08:15 -04:00
|
|
|
|
(let ((uid (format "TS%d-%s" (cl-incf counter) uid)))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(org-icalendar--vevent
|
2017-08-11 16:18:06 -04:00
|
|
|
|
entry ts uid summary loc desc cat tz class))))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
info nil (and (eq type 'headline) 'inlinetask))
|
|
|
|
|
""))
|
|
|
|
|
;; Task: First check if it is appropriate to export it. If
|
|
|
|
|
;; so, call `org-icalendar--vtodo' to transcode it into
|
|
|
|
|
;; a "VTODO" component.
|
|
|
|
|
(when (and todo-type
|
2016-05-26 10:08:15 -04:00
|
|
|
|
(cl-case (plist-get info :icalendar-include-todo)
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(all t)
|
|
|
|
|
(unblocked
|
|
|
|
|
(and (eq type 'headline)
|
|
|
|
|
(not (org-icalendar-blocked-headline-p
|
|
|
|
|
entry info))))
|
|
|
|
|
((t) (eq todo-type 'todo))))
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(org-icalendar--vtodo entry uid summary loc desc cat tz class))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
;; Diary-sexp: Collect every diary-sexp element within ENTRY
|
|
|
|
|
;; and its title, and transcode them. If ENTRY is
|
|
|
|
|
;; a headline, skip inlinetasks: they will be handled
|
|
|
|
|
;; separately.
|
|
|
|
|
(when org-icalendar-include-sexps
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(let ((counter 0))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(mapconcat #'identity
|
|
|
|
|
(org-element-map
|
|
|
|
|
(cons (org-element-property :title entry)
|
|
|
|
|
(org-element-contents inside))
|
|
|
|
|
'diary-sexp
|
|
|
|
|
(lambda (sexp)
|
|
|
|
|
(org-icalendar-transcode-diary-sexp
|
|
|
|
|
(org-element-property :value sexp)
|
2016-05-26 10:08:15 -04:00
|
|
|
|
(format "DS%d-%s" (cl-incf counter) uid)
|
2014-08-25 18:08:38 -04:00
|
|
|
|
summary))
|
|
|
|
|
info nil (and (eq type 'headline) 'inlinetask))
|
|
|
|
|
"")))))
|
2013-02-23 07:47:44 -05:00
|
|
|
|
;; If ENTRY is a headline, call current function on every
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; inlinetask within it. In agenda export, this is independent
|
|
|
|
|
;; from the mark (or lack thereof) on the entry.
|
|
|
|
|
(when (eq type 'headline)
|
2014-04-23 15:31:34 -04:00
|
|
|
|
(mapconcat #'identity
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(org-element-map inside 'inlinetask
|
|
|
|
|
(lambda (task) (org-icalendar-entry task nil info))
|
|
|
|
|
info) ""))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; Don't forget components from inner entries.
|
|
|
|
|
contents))))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar--vevent
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(entry timestamp uid summary location description categories timezone class)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Create a VEVENT component.
|
|
|
|
|
|
2013-02-23 07:47:44 -05:00
|
|
|
|
ENTRY is either a headline or an inlinetask element. TIMESTAMP
|
2012-09-14 09:29:39 -04:00
|
|
|
|
is a timestamp object defining the date-time of the event. UID
|
|
|
|
|
is the unique identifier for the event. SUMMARY defines a short
|
|
|
|
|
summary or subject for the event. LOCATION defines the intended
|
|
|
|
|
venue for the event. DESCRIPTION provides the complete
|
|
|
|
|
description of the event. CATEGORIES defines the categories the
|
2017-08-19 18:11:18 -04:00
|
|
|
|
event belongs to. TIMEZONE specifies a time zone for this event
|
2017-08-11 16:18:06 -04:00
|
|
|
|
only. CLASS contains the visibility attribute. Three of them
|
|
|
|
|
(\"PUBLIC\", \"CONFIDENTIAL\", and \"PRIVATE\") are predefined, others
|
|
|
|
|
should be treated as \"PRIVATE\" if they are unknown to the iCalendar server.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
Return VEVENT component as a string."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(org-icalendar-fold-string
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(if (eq (org-element-property :type timestamp) 'diary)
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(org-icalendar-transcode-diary-sexp
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(org-element-property :raw-value timestamp) uid summary)
|
|
|
|
|
(concat "BEGIN:VEVENT\n"
|
2013-04-05 15:28:35 -04:00
|
|
|
|
(org-icalendar-dtstamp) "\n"
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"UID:" uid "\n"
|
2017-08-19 18:11:18 -04:00
|
|
|
|
(org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n"
|
|
|
|
|
(org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n"
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; RRULE.
|
|
|
|
|
(when (org-element-property :repeater-type timestamp)
|
|
|
|
|
(format "RRULE:FREQ=%s;INTERVAL=%d\n"
|
2016-05-26 10:08:15 -04:00
|
|
|
|
(cl-case (org-element-property :repeater-unit timestamp)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(hour "HOURLY") (day "DAILY") (week "WEEKLY")
|
|
|
|
|
(month "MONTHLY") (year "YEARLY"))
|
|
|
|
|
(org-element-property :repeater-value timestamp)))
|
|
|
|
|
"SUMMARY:" summary "\n"
|
|
|
|
|
(and (org-string-nw-p location) (format "LOCATION:%s\n" location))
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(and (org-string-nw-p class) (format "CLASS:%s\n" class))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(and (org-string-nw-p description)
|
|
|
|
|
(format "DESCRIPTION:%s\n" description))
|
|
|
|
|
"CATEGORIES:" categories "\n"
|
|
|
|
|
;; VALARM.
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(org-icalendar--valarm entry timestamp summary)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"END:VEVENT"))))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar--vtodo
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(entry uid summary location description categories timezone class)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Create a VTODO component.
|
|
|
|
|
|
2013-02-23 07:47:44 -05:00
|
|
|
|
ENTRY is either a headline or an inlinetask element. UID is the
|
2012-09-14 09:29:39 -04:00
|
|
|
|
unique identifier for the task. SUMMARY defines a short summary
|
|
|
|
|
or subject for the task. LOCATION defines the intended venue for
|
|
|
|
|
the task. DESCRIPTION provides the complete description of the
|
|
|
|
|
task. CATEGORIES defines the categories the task belongs to.
|
2017-08-19 18:11:18 -04:00
|
|
|
|
TIMEZONE specifies a time zone for this TODO only.
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
Return VTODO component as a string."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(let ((start (or (and (memq 'todo-start org-icalendar-use-scheduled)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(org-element-property :scheduled entry))
|
|
|
|
|
;; If we can't use a scheduled time for some
|
|
|
|
|
;; reason, start task now.
|
2014-10-28 21:42:51 -04:00
|
|
|
|
(let ((now (decode-time)))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(list 'timestamp
|
|
|
|
|
(list :type 'active
|
|
|
|
|
:minute-start (nth 1 now)
|
|
|
|
|
:hour-start (nth 2 now)
|
|
|
|
|
:day-start (nth 3 now)
|
|
|
|
|
:month-start (nth 4 now)
|
|
|
|
|
:year-start (nth 5 now)))))))
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(org-icalendar-fold-string
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(concat "BEGIN:VTODO\n"
|
|
|
|
|
"UID:TODO-" uid "\n"
|
2013-04-05 15:28:35 -04:00
|
|
|
|
(org-icalendar-dtstamp) "\n"
|
2017-08-19 18:11:18 -04:00
|
|
|
|
(org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n"
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(and (memq 'todo-due org-icalendar-use-deadline)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(org-element-property :deadline entry)
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(concat (org-icalendar-convert-timestamp
|
2017-08-19 18:11:18 -04:00
|
|
|
|
(org-element-property :deadline entry) "DUE" nil timezone)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"\n"))
|
|
|
|
|
"SUMMARY:" summary "\n"
|
|
|
|
|
(and (org-string-nw-p location) (format "LOCATION:%s\n" location))
|
2017-08-11 16:18:06 -04:00
|
|
|
|
(and (org-string-nw-p class) (format "CLASS:%s\n" class))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(and (org-string-nw-p description)
|
|
|
|
|
(format "DESCRIPTION:%s\n" description))
|
|
|
|
|
"CATEGORIES:" categories "\n"
|
|
|
|
|
"SEQUENCE:1\n"
|
|
|
|
|
(format "PRIORITY:%d\n"
|
|
|
|
|
(let ((pri (or (org-element-property :priority entry)
|
|
|
|
|
org-default-priority)))
|
|
|
|
|
(floor (- 9 (* 8. (/ (float (- org-lowest-priority pri))
|
|
|
|
|
(- org-lowest-priority
|
|
|
|
|
org-highest-priority)))))))
|
|
|
|
|
(format "STATUS:%s\n"
|
|
|
|
|
(if (eq (org-element-property :todo-type entry) 'todo)
|
|
|
|
|
"NEEDS-ACTION"
|
|
|
|
|
"COMPLETED"))
|
|
|
|
|
"END:VTODO"))))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar--valarm (entry timestamp summary)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Create a VALARM component.
|
|
|
|
|
|
|
|
|
|
ENTRY is the calendar entry triggering the alarm. TIMESTAMP is
|
|
|
|
|
the start date-time of the entry. SUMMARY defines a short
|
|
|
|
|
summary or subject for the task.
|
|
|
|
|
|
|
|
|
|
Return VALARM component as a string, or nil if it isn't allowed."
|
|
|
|
|
;; Create a VALARM entry if the entry is timed. This is not very
|
|
|
|
|
;; general in that:
|
|
|
|
|
;; (a) only one alarm per entry is defined,
|
|
|
|
|
;; (b) only minutes are allowed for the trigger period ahead of the
|
|
|
|
|
;; start time,
|
|
|
|
|
;; (c) only a DISPLAY action is defined. [ESF]
|
|
|
|
|
(let ((alarm-time
|
|
|
|
|
(let ((warntime
|
export-back-ends: Apply changes from b692064e621acbc93876670585f8a4b0fd6a7ffa
* lisp/ox-beamer.el (org-beamer--get-label, org-beamer--frame-level,
org-beamer--format-section, org-beamer--format-frame,
org-beamer--format-block, org-beamer-headline): Apply changes to
properties.
* lisp/ox-html.el (org-html-headline, org-html-link,
org-html-section): Apply changes to properties.
* lisp/ox-icalendar.el (org-icalendar-create-uid,
org-icalendar-blocked-headline-p, org-icalendar-entry,
org-icalendar--valarm): Apply changes to properties.
* lisp/ox-odt.el (org-odt-headline): Apply changes
* lisp/ox-publish.el (org-publish-collect-index): Apply changes to
properties.
* lisp/ox-texinfo.el (org-texinfo--generate-menu-list,
org-texinfo--generate-menu-items, org-texinfo-template,
org-texinfo-headline, org-texinfo-link): Apply changes to
properties.
* lisp/ox.el (org-export-resolve-id-link, org-export-get-category):
Apply changes to properties.
(org-export-get-node-property): Update docstring.
* testing/lisp/test-ox.el: Update tests.
2013-02-17 18:01:21 -05:00
|
|
|
|
(org-element-property :APPT_WARNTIME entry)))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(if warntime (string-to-number warntime) 0))))
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(and (or (> alarm-time 0) (> org-icalendar-alarm-time 0))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(org-element-property :hour-start timestamp)
|
|
|
|
|
(format "BEGIN:VALARM
|
|
|
|
|
ACTION:DISPLAY
|
|
|
|
|
DESCRIPTION:%s
|
|
|
|
|
TRIGGER:-P0DT0H%dM0S
|
|
|
|
|
END:VALARM\n"
|
|
|
|
|
summary
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(if (zerop alarm-time) org-icalendar-alarm-time alarm-time)))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;; Template
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-template (contents info)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Return complete document string after iCalendar conversion.
|
|
|
|
|
CONTENTS is the transcoded contents string. INFO is a plist used
|
|
|
|
|
as a communication channel."
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(org-icalendar--vcalendar
|
2012-09-14 09:29:39 -04:00
|
|
|
|
;; Name.
|
|
|
|
|
(if (not (plist-get info :input-file)) (buffer-name (buffer-base-buffer))
|
|
|
|
|
(file-name-nondirectory
|
|
|
|
|
(file-name-sans-extension (plist-get info :input-file))))
|
|
|
|
|
;; Owner.
|
|
|
|
|
(if (not (plist-get info :with-author)) ""
|
|
|
|
|
(org-export-data (plist-get info :author) info))
|
|
|
|
|
;; Timezone.
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(if (org-string-nw-p org-icalendar-timezone) org-icalendar-timezone
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(cadr (current-time-zone)))
|
|
|
|
|
;; Description.
|
|
|
|
|
(org-export-data (plist-get info :title) info)
|
|
|
|
|
contents))
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar--vcalendar (name owner tz description contents)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Create a VCALENDAR component.
|
|
|
|
|
NAME, OWNER, TZ, DESCRIPTION and CONTENTS are all strings giving,
|
|
|
|
|
respectively, the name of the calendar, its owner, the timezone
|
|
|
|
|
used, a short description and the other components included."
|
|
|
|
|
(concat (format "BEGIN:VCALENDAR
|
|
|
|
|
VERSION:2.0
|
|
|
|
|
X-WR-CALNAME:%s
|
|
|
|
|
PRODID:-//%s//Emacs with Org mode//EN
|
|
|
|
|
X-WR-TIMEZONE:%s
|
|
|
|
|
X-WR-CALDESC:%s
|
|
|
|
|
CALSCALE:GREGORIAN\n"
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(org-icalendar-cleanup-string name)
|
|
|
|
|
(org-icalendar-cleanup-string owner)
|
|
|
|
|
(org-icalendar-cleanup-string tz)
|
|
|
|
|
(org-icalendar-cleanup-string description))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
contents
|
|
|
|
|
"END:VCALENDAR\n"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Interactive Functions
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-export-to-ics
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(&optional async subtreep visible-only body-only)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Export current buffer to an iCalendar file.
|
|
|
|
|
|
|
|
|
|
If narrowing is active in the current buffer, only export its
|
|
|
|
|
narrowed part.
|
|
|
|
|
|
|
|
|
|
If a region is active, export that region.
|
|
|
|
|
|
2012-12-02 11:24:19 -05:00
|
|
|
|
A non-nil optional argument ASYNC means the process should happen
|
|
|
|
|
asynchronously. The resulting file should be accessible through
|
|
|
|
|
the `org-export-stack' interface.
|
|
|
|
|
|
2012-09-14 09:29:39 -04:00
|
|
|
|
When optional argument SUBTREEP is non-nil, export the sub-tree
|
|
|
|
|
at point, extracting information from the headline properties
|
|
|
|
|
first.
|
|
|
|
|
|
|
|
|
|
When optional argument VISIBLE-ONLY is non-nil, don't export
|
|
|
|
|
contents of hidden elements.
|
|
|
|
|
|
|
|
|
|
When optional argument BODY-ONLY is non-nil, only write code
|
|
|
|
|
between \"BEGIN:VCALENDAR\" and \"END:VCALENDAR\".
|
|
|
|
|
|
|
|
|
|
Return ICS file name."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((file (buffer-file-name (buffer-base-buffer))))
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(when (and file org-icalendar-store-UID)
|
|
|
|
|
(org-icalendar-create-uid file 'warn-user)))
|
2013-03-06 11:10:26 -05:00
|
|
|
|
;; Export part. Since this back-end is backed up by `ascii', ensure
|
|
|
|
|
;; links will not be collected at the end of sections.
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(let ((outfile (org-export-output-file-name ".ics" subtreep)))
|
Export back-ends: Apply changes to export functions
* contrib/lisp/ox-confluence.el (org-confluence-export-as-confluence):
* contrib/lisp/ox-deck.el (org-deck-export-as-html,
org-deck-export-to-html):
* contrib/lisp/ox-freemind.el (org-freemind-export-to-freemind):
* contrib/lisp/ox-groff.el (org-groff-export-to-groff,
org-groff-export-to-pdf):
* contrib/lisp/ox-koma-letter.el (org-koma-letter-export-as-latex,
org-koma-letter-export-to-latex, org-koma-letter-export-to-pdf):
* contrib/lisp/ox-rss.el (org-rss-export-as-rss,
org-rss-export-to-rss):
* contrib/lisp/ox-s5.el (org-s5-export-as-html,
org-s5-export-to-html):
* contrib/lisp/ox-taskjuggler.el (org-taskjuggler-export):
* lisp/ob-haskell.el:
* lisp/ox-ascii.el (org-ascii-export-as-ascii,
org-ascii-export-to-ascii):
* lisp/ox-beamer.el (org-beamer-export-as-latex,
org-beamer-export-to-latex, org-beamer-export-to-pdf):
* lisp/ox-html.el (org-html-export-as-html, org-html-export-to-html):
* lisp/ox-icalendar.el (org-icalendar-export-to-ics):
* lisp/ox-latex.el (org-latex-export-as-latex,
org-latex-export-to-pdf):
* lisp/ox-man.el (org-man-export-to-man, org-man-export-to-pdf):
* lisp/ox-md.el (org-md-export-as-markdown,
org-md-export-to-markdown):
* lisp/ox-odt.el (org-odt-export-to-odt):
* lisp/ox-org.el (org-org-export-as-org, org-org-export-to-org):
* lisp/ox-publish.el (org-publish-org-to):
* lisp/ox-texinfo.el (org-texinfo-export-to-texinfo,
org-texinfo-export-to-info):
* testing/lisp/test-ob-exp.el (test-ob-exp/org-babel-exp-src-blocks/w-no-file):
2013-08-07 04:35:42 -04:00
|
|
|
|
(org-export-to-file 'icalendar outfile
|
2014-08-25 18:08:38 -04:00
|
|
|
|
async subtreep visible-only body-only
|
|
|
|
|
'(:ascii-charset utf-8 :ascii-links-to-notes nil)
|
Export back-ends: Apply changes to export functions
* contrib/lisp/ox-confluence.el (org-confluence-export-as-confluence):
* contrib/lisp/ox-deck.el (org-deck-export-as-html,
org-deck-export-to-html):
* contrib/lisp/ox-freemind.el (org-freemind-export-to-freemind):
* contrib/lisp/ox-groff.el (org-groff-export-to-groff,
org-groff-export-to-pdf):
* contrib/lisp/ox-koma-letter.el (org-koma-letter-export-as-latex,
org-koma-letter-export-to-latex, org-koma-letter-export-to-pdf):
* contrib/lisp/ox-rss.el (org-rss-export-as-rss,
org-rss-export-to-rss):
* contrib/lisp/ox-s5.el (org-s5-export-as-html,
org-s5-export-to-html):
* contrib/lisp/ox-taskjuggler.el (org-taskjuggler-export):
* lisp/ob-haskell.el:
* lisp/ox-ascii.el (org-ascii-export-as-ascii,
org-ascii-export-to-ascii):
* lisp/ox-beamer.el (org-beamer-export-as-latex,
org-beamer-export-to-latex, org-beamer-export-to-pdf):
* lisp/ox-html.el (org-html-export-as-html, org-html-export-to-html):
* lisp/ox-icalendar.el (org-icalendar-export-to-ics):
* lisp/ox-latex.el (org-latex-export-as-latex,
org-latex-export-to-pdf):
* lisp/ox-man.el (org-man-export-to-man, org-man-export-to-pdf):
* lisp/ox-md.el (org-md-export-as-markdown,
org-md-export-to-markdown):
* lisp/ox-odt.el (org-odt-export-to-odt):
* lisp/ox-org.el (org-org-export-as-org, org-org-export-to-org):
* lisp/ox-publish.el (org-publish-org-to):
* lisp/ox-texinfo.el (org-texinfo-export-to-texinfo,
org-texinfo-export-to-info):
* testing/lisp/test-ob-exp.el (test-ob-exp/org-babel-exp-src-blocks/w-no-file):
2013-08-07 04:35:42 -04:00
|
|
|
|
(lambda (file)
|
|
|
|
|
(run-hook-with-args 'org-icalendar-after-save-hook file) nil))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
;;;###autoload
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-export-agenda-files (&optional async)
|
2012-12-02 11:24:19 -05:00
|
|
|
|
"Export all agenda files to iCalendar files.
|
|
|
|
|
When optional argument ASYNC is non-nil, export happens in an
|
|
|
|
|
external process."
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(interactive)
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(if async
|
|
|
|
|
;; Asynchronous export is not interactive, so we will not call
|
|
|
|
|
;; `org-check-agenda-file'. Instead we remove any non-existent
|
|
|
|
|
;; agenda file from the list.
|
2015-11-06 07:04:20 -05:00
|
|
|
|
(let ((files (cl-remove-if-not #'file-exists-p (org-agenda-files t))))
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(org-export-async-start
|
|
|
|
|
(lambda (results)
|
2015-10-29 13:53:03 -04:00
|
|
|
|
(dolist (f results) (org-export-add-to-stack f 'icalendar)))
|
2012-12-02 11:24:19 -05:00
|
|
|
|
`(let (output-files)
|
2015-10-29 13:53:03 -04:00
|
|
|
|
(dolist (file ',files outputfiles)
|
|
|
|
|
(with-current-buffer (org-get-agenda-file-buffer file)
|
|
|
|
|
(push (expand-file-name (org-icalendar-export-to-ics))
|
|
|
|
|
output-files))))))
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(let ((files (org-agenda-files t)))
|
|
|
|
|
(org-agenda-prepare-buffers files)
|
|
|
|
|
(unwind-protect
|
2015-10-29 13:53:03 -04:00
|
|
|
|
(dolist (file files)
|
|
|
|
|
(catch 'nextfile
|
|
|
|
|
(org-check-agenda-file file)
|
|
|
|
|
(with-current-buffer (org-get-agenda-file-buffer file)
|
|
|
|
|
(org-icalendar-export-to-ics))))
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(org-release-buffers org-agenda-new-buffers)))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
;;;###autoload
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(defun org-icalendar-combine-agenda-files (&optional async)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Combine all agenda files into a single iCalendar file.
|
2012-12-02 11:24:19 -05:00
|
|
|
|
|
|
|
|
|
A non-nil optional argument ASYNC means the process should happen
|
|
|
|
|
asynchronously. The resulting file should be accessible through
|
|
|
|
|
the `org-export-stack' interface.
|
|
|
|
|
|
|
|
|
|
The file is stored under the name chosen in
|
2013-01-27 17:11:34 -05:00
|
|
|
|
`org-icalendar-combined-agenda-file'."
|
2012-09-14 09:29:39 -04:00
|
|
|
|
(interactive)
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(if async
|
2015-11-06 07:04:20 -05:00
|
|
|
|
(let ((files (cl-remove-if-not #'file-exists-p (org-agenda-files t))))
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(org-export-async-start
|
2015-10-29 13:53:03 -04:00
|
|
|
|
(lambda (_)
|
2012-12-02 11:24:19 -05:00
|
|
|
|
(org-export-add-to-stack
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(expand-file-name org-icalendar-combined-agenda-file)
|
|
|
|
|
'icalendar))
|
2015-10-29 13:53:03 -04:00
|
|
|
|
`(apply #'org-icalendar--combine-files ',files)))
|
|
|
|
|
(apply #'org-icalendar--combine-files (org-agenda-files t))))
|
2013-01-27 17:11:34 -05:00
|
|
|
|
|
|
|
|
|
(defun org-icalendar-export-current-agenda (file)
|
|
|
|
|
"Export current agenda view to an iCalendar FILE.
|
|
|
|
|
This function assumes major mode for current buffer is
|
|
|
|
|
`org-agenda-mode'."
|
2017-08-05 05:05:45 -04:00
|
|
|
|
(let* ((org-export-use-babel) ;don't evaluate Babel blocks
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(contents
|
|
|
|
|
(org-export-string-as
|
|
|
|
|
(with-output-to-string
|
|
|
|
|
(save-excursion
|
2017-08-05 05:02:14 -04:00
|
|
|
|
(let ((p (point-min))
|
|
|
|
|
(seen nil)) ;prevent duplicates
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(while (setq p (next-single-property-change p 'org-hd-marker))
|
|
|
|
|
(let ((m (get-text-property p 'org-hd-marker)))
|
2017-08-05 19:19:49 -04:00
|
|
|
|
(when (and m (not (member m seen)))
|
2017-08-05 05:02:14 -04:00
|
|
|
|
(push m seen)
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(with-current-buffer (marker-buffer m)
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
(goto-char (marker-position m))
|
|
|
|
|
(princ
|
|
|
|
|
(org-element-normalize-string
|
2017-08-05 05:02:14 -04:00
|
|
|
|
(buffer-substring (point)
|
|
|
|
|
(org-entry-end-position))))))))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(forward-line)))))
|
2015-11-26 08:20:42 -05:00
|
|
|
|
'icalendar t
|
|
|
|
|
'(:ascii-charset utf-8 :ascii-links-to-notes nil
|
|
|
|
|
:icalendar-include-todo all))))
|
2014-08-25 18:08:38 -04:00
|
|
|
|
(with-temp-file file
|
|
|
|
|
(insert
|
|
|
|
|
(org-icalendar--vcalendar
|
|
|
|
|
org-icalendar-combined-name
|
|
|
|
|
user-full-name
|
|
|
|
|
(or (org-string-nw-p org-icalendar-timezone) (cadr (current-time-zone)))
|
2016-01-21 20:28:58 -05:00
|
|
|
|
org-icalendar-combined-description
|
2014-08-25 18:08:38 -04:00
|
|
|
|
contents)))
|
|
|
|
|
(run-hook-with-args 'org-icalendar-after-save-hook file)))
|
|
|
|
|
|
|
|
|
|
(defun org-icalendar--combine-files (&rest files)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
"Combine entries from multiple files into an iCalendar file.
|
2014-08-25 18:08:38 -04:00
|
|
|
|
FILES is a list of files to build the calendar from."
|
2017-08-31 09:58:51 -04:00
|
|
|
|
;; At the end of the process, all buffers related to FILES are going
|
|
|
|
|
;; to be killed. Make sure to only kill the ones opened in the
|
|
|
|
|
;; process.
|
|
|
|
|
(let ((org-agenda-new-buffers nil))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
|
|
|
|
(with-temp-file org-icalendar-combined-agenda-file
|
|
|
|
|
(insert
|
|
|
|
|
(org-icalendar--vcalendar
|
|
|
|
|
;; Name.
|
|
|
|
|
org-icalendar-combined-name
|
|
|
|
|
;; Owner.
|
|
|
|
|
user-full-name
|
|
|
|
|
;; Timezone.
|
|
|
|
|
(or (org-string-nw-p org-icalendar-timezone)
|
|
|
|
|
(cadr (current-time-zone)))
|
|
|
|
|
;; Description.
|
|
|
|
|
org-icalendar-combined-description
|
|
|
|
|
;; Contents.
|
|
|
|
|
(concat
|
|
|
|
|
;; Agenda contents.
|
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (file)
|
|
|
|
|
(catch 'nextfile
|
|
|
|
|
(org-check-agenda-file file)
|
|
|
|
|
(with-current-buffer (org-get-agenda-file-buffer file)
|
|
|
|
|
;; Create ID if necessary.
|
|
|
|
|
(when org-icalendar-store-UID
|
|
|
|
|
(org-icalendar-create-uid file t))
|
|
|
|
|
(org-export-as
|
|
|
|
|
'icalendar nil nil t
|
|
|
|
|
'(:ascii-charset utf-8 :ascii-links-to-notes nil)))))
|
|
|
|
|
files "")
|
|
|
|
|
;; BBDB anniversaries.
|
|
|
|
|
(when (and org-icalendar-include-bbdb-anniversaries
|
|
|
|
|
(require 'org-bbdb nil t))
|
|
|
|
|
(with-output-to-string (org-bbdb-anniv-export-ical)))))))
|
|
|
|
|
(run-hook-with-args 'org-icalendar-after-save-hook
|
|
|
|
|
org-icalendar-combined-agenda-file))
|
|
|
|
|
(org-release-buffers org-agenda-new-buffers))))
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
(provide 'ox-icalendar)
|
2012-09-14 09:29:39 -04:00
|
|
|
|
|
2013-01-27 17:11:34 -05:00
|
|
|
|
;; Local variables:
|
|
|
|
|
;; generated-autoload-file: "org-loaddefs.el"
|
|
|
|
|
;; End:
|
|
|
|
|
|
|
|
|
|
;;; ox-icalendar.el ends here
|