Thanks a lot Victor Much appreciated
I use your script to create event. Only one thing is missing, the duration of the event I can’t set it
Any idea on this?
For reminder it is a very good approach when multiple reminder must be created
Thanks again!!
What do you mean? I am not sure what you are trying to accomplish.
Hi Victor
I m using time blocking concept in my agenda
Where I block a certain amount of time for a specific task
May be a @duration tag will help me to have an event duration in my calendar from a task in task paper
It will block the allowed time for this task in my agenda
AD I m not a dev and your script is just amazing providing a very good start in my workflow may be you can help here
Thanks
Patrick
Hey Patrick. This script is from Brett. I would recommend you to read his documentation and explanation (there was a link to it on the original post.) I am also not a dev, but I can figure things out with some help here and there. I think (Not sure right now if I or someone here help me to accomplish this) that all I did was add an AppleScript to add this to the calendar. The AppleScript is doing all the magic.
Right now the script reserves a block of 60 min. Adding a duration tag seems to be doable. Just let me ask you the following.
Is the script working right now in your computer?
If it is working, even if it is not doing what you want, then I can help you figure things out. It will just take me some time for me to remember. Add another post and tag me, and we will figure things there.
I checked the script. Honestly, it should be rewritten to use the ruby library to make it easy to understand and modify. Like I said, I didn’t write this script to begin with. I modified it.
Although for what you are trying to do, it may be better to make a script that you can run with Alfred or KeyboardMaestro or something like that. Maybe @complexpoint can help you with this. It seems as if what you want to do is look for a tag like @reserve_time(2021-03-03 13:01) @duration(60) where the number inside the duration tag is “minutes.” The script can change the @reserve_time to @reserved_time once the event has been added to the calendar. Is that what you are thinking of @Patrick68?
Hi Victor,
Thanks a lot for the time spend here
Yes it is exactly what I would like to have but no need to create a @reserve_time Tag just needed the @duration tag used in conjunction with @event tag If I have the duration tag I can change the default duration in “minutes” of the specified event I would create. I@duration exist the script will change the duration otherwyse it will keep the default duration.
This will allow me to use time block for my task where I know the duration / the allowed time.
Thanks again
Patrick
Hi All
I have amended the script provided by @Victor to add the @durartion in min
Enjoy with it
#!/usr/bin/ruby
# == Synopsis
# This tool will search for @remind() tags in the specified notes folder.
#
# It searches ".md", ".txt", ".ft" and ".taskpaper" files. It also works with Day One journal folders.
#
# It expects an ISO 8601 format date (2013-05-01) with optional 24-hour time (2013-05-01 15:30).
# Put `@remind(2013-05-01 06:00)` anywhere in a note to have a reminder go off on the first run after that time.
#
#
# Reminders on their own line with no other text will send the entire note as the reminder with the filename being the subject line. If a @reminder tag is on a line with other text, only that line will be used as the title and the content.
#
# If you include a double-quoted string at the end of the remind tag value, it will override the default reminder title. `@remind(2013-05-24 "This is the override")` would create a reminder called "This is the override", ignoring any other text on the line or the name of the file. Additional text on the line or the entire note (in the case of a @remind tag on its own line) will still be included in the note, if the notification method supports that.
#
# This script is intended to be run on a schedule. Check for reminders every 30-60 minutes using cron or launchd.
#
# Use the -n option to send Mountain Lion notifications instead of terminal output. Clicking a notification will open the related file in nvALT.
# Notifications require that the 'terminal-notifier' gem be installed:
#
# sudo gem install 'terminal-notifier'
#
# Use the -e ADDRESS option to send an email with the title of the note as the subject and the contents of the note as the body to the specified address. Separate multiple emails with commas. The contents of the note will be rendered with MultiMarkdown, which needs to exist at /usr/local/bin/multimarkdown.
#
# If the file has a ".taskpaper" extension, it will be converted to Markdown for formatting before processing with MultiMarkdown.
#
# The `-m` option will add a reminder to Reminders.app in Mountain Lion, due immediately, that will show up on iCloud-synced iOS devices as well.
#
# The `-f FOLDER` option allows you to specify a directory where a file named with the reminder title will be saved. The note for the reminder will be the file contents. This is useful, for example, with IFTTT.com. You can save a file to a public Dropbox folder, have IFTTT notice it and take any number of actions on it.
# == Examples
#
# nvremind.rb ~/Dropbox/nvALT
#
# Other examples:
# nvremind.rb ~/Dropbox/nvALT
# nvremind.rb -n ~/Dropbox/nvALT
# nvremind.rb -e me@gmail.com ~/Dropbox/nvALT
# nvremind.rb -mn -e me@gmail.com ~/Dropbox/nvALT
# == Usage
# nvremind.rb [options] notes_folder
#
# For help use: nvremind.rb -h
#
# See <http://brettterpstra.com/projects/nvremind> for more information
#
# == Options
# -h, --help Displays help message
# -v, --version Display the version, then exit
# -V, --verbose Verbose output
# -z, --no-replace Don't updated @remind() tags with @reminded() after notification
# -n, --notify Use terminal-notifier to post Mountain Lion notifications
# -m, --reminders Add an item to the Reminders list in Reminders.app (due immediately)
# --reminder-list LIST List to use in Reminders.app (default "Reminders")
# -f folder Save a file to FOLDER named with the task title, note as contents
# -e EMAIL[,EMAIL], --email EMAIL[,EMAIL] Send an email with note contents to the specified address
#
# == Author
# Brett Terpstra
#
# == Copyright
# Copyright (c) 2013 Brett Terpstra. Licensed under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
require 'date'
require 'cgi'
require 'time'
require 'optparse'
require 'ostruct'
require 'shellwords'
NVR_VERSION = '1.0.6'.freeze
if RUBY_VERSION.to_f > 1.9
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
end
class TaskPaper
def tp2md(input)
header = input.scan(/Format\: .*$/)
output = ''
prevlevel = 0
begin
input.split("\n").each do|line|
if line =~ /^(\t+)?(.*?):(\s(.*?))?$/
tabs = Regexp.last_match(1)
project = Regexp.last_match(2)
if tabs.nil?
output += "\n## #{project} ##\n\n"
prevlevel = 0
else
output += "#{tabs.gsub(/^\t/, '')}* **#{project.gsub(/^\s*-\s*/, '')}**\n"
prevlevel = tabs.length
end
elsif line =~ /^(\t+)?\- (.*)$/
task = Regexp.last_match(2)
tabs = Regexp.last_match(1).nil? ? '' : Regexp.last_match(1)
task = "*<del>#{task}</del>*" if task =~ /@done/
if tabs.length - prevlevel > 1
tabs = "\t"
prevlevel.times { |_i| tabs += "\t" }
end
tabs = '' if prevlevel == 0 && tabs.length > 1
output += "#{tabs.gsub(/^\t/, '')}* #{task.strip}\n"
prevlevel = tabs.length
else
next if line =~ /^\s*$/
tabs = ''
(prevlevel - 1).times { |_i| tabs += "\t" }
output += "\n#{tabs}*#{line.strip}*\n"
end
end
rescue => err
puts "Exception: #{err}"
err
end
o = ''
o += header.join("\n") + "\n" unless header.nil?
o += '<style>.tag strong {font-weight:normal;color:#555} .tag a {text-decoration:none;border:none;color:#777}</style>'
# o += output.gsub(/\[\[(.*?)\]\]/,"<a href=\"nvalt://find/\\1\">\\1</a>").gsub(/(@[^ \n\r\(]+)((\()([^\)]+)(\)))?/,"<em class=\"tag\"><a href=\"nvalt://find/\\0\">\\1\\3<strong>\\4</strong>\\5</a></em>")
o
end
end
class Reminder
attr_reader :options
def initialize(arguments)
@arguments = arguments
@options = OpenStruct.new
@options.remove = true
@options.preserve_time = true
@options.verbose = false
@options.notify = false
@options.email = false
@options.file = false
@options.stdout = true
@options.reminders = false
@options.reminder_list = 'Reminders'
end
def run
if parsed_options? && arguments_valid?
puts "Start at #{DateTime.now}\n\n" if @options.verbose
output_options if @options.verbose # [Optional]
process_arguments
process_command
puts "\nFinished at #{DateTime.now}" if @options.verbose
else
output_usage
end
end
def e_as(str)
str.to_s.gsub(/(?=["\\])/, '\\')
end
protected
def parsed_options?
opts = OptionParser.new
opts.on('-v', '--version', 'Display version information') { output_version; exit 0 }
opts.on('-V', '--verbose', 'Verbose output') { @options.verbose = true }
opts.on('-z', '--no-replace', "Don't updated @remind() tags with @reminded() after notification") { @options.remove = false }
opts.on('--no-preserve-time', 'Allow file modification time to change') { @options.preserve_time = false }
opts.on('-n', '--notify', 'Use terminal-notifier to post Mountain Lion notifications') { @options.notify = true }
opts.on('-r', '--replace', 'Deprecated, no effect') {} # deprecated, backward compatibility only
opts.on('-m', '--reminders', 'Add an item to the Reminders list in Reminders.app (due immediately)') { @options.reminders = true }
opts.on('--reminder-list LIST', "List to use in Reminders.app (default 'Reminders')") { |list| @options.reminder_list = list }
opts.on('-f FOLDER', '--file FOLDER', 'Add a file to the specified folder') do |folder|
if File.exist?(File.expand_path(folder))
@options.file = File.expand_path(folder)
else
puts "Invalid folder specified for -f (#{folder} does not exist)"
Process.exit 1
end
end
opts.on('-e EMAIL[,EMAIL]', '--email EMAIL[,EMAIL]', 'Send an email with note contents to the specified address') do |emails|
@options.email = []
emails.split(/,/).each do|email|
@options.email.push(email.strip)
end
end
opts.on('-h', '--help', 'Display this screen') do
puts opts
puts
output_usage
end
begin
opts.parse!(@arguments)
rescue
return false
end
true
end
def output_options
puts "Options:\n"
@options.marshal_dump.each do |name, val|
puts " #{name} = #{val}"
end
end
def arguments_valid?
@notes_dir = []
unless @arguments[0].nil?
@arguments[0].split(',').each do|path|
@notes_dir.push(File.expand_path(path)) if File.exist?(File.expand_path(path))
end
true unless @notes_dir.empty?
else
false
end
end
def process_arguments
if @options.notify
begin
require 'rubygems'
gem 'terminal-notifier', '>=1.4'
require 'terminal-notifier'
@options.notify = 'terminal-notifier'
rescue Gem::LoadError
@options.notify = `growlnotify &>/dev/null && echo $? || echo false`.strip == '0' ? 'growlnotify' : false
$stderr.puts 'Either terminal-notifier gem or growlnotify must be installed to use Notifications' unless @options.notify
end
end
if (@options.notify || @options.email || @options.reminders) && !@options.verbose
@options.stdout = false
end
end
def output_usage
output_version
usage = <<ENDUSAGE
nvremind.rb [options] notes_folder
For help use: nvremind.rb -h
See <http://brettterpstra.com/projects/nvremind> for more information
ENDUSAGE
puts usage
Process.exit
end
def output_version
puts "#{File.basename(__FILE__)} version #{NVR_VERSION}"
end
def process_command
@notes_dir.each do|notes_dir|
Dir.chdir(notes_dir)
puts
`grep -El "[^\\s]remind\\(.*?\\)|[^\\s]event\\(.*?\\)" *.{md,txt,taskpaper,ft,doentry} 2>/dev/null`.split("\n").each do|file|
mod_time = File.mtime(file)
input = if RUBY_VERSION.to_f > 1.9
IO.read(file).force_encoding('utf-8')
else
IO.read(file)
end
lines = input.split(/\n/)
counter = 0
lines.map! do|contents|
counter += 1
# don't remind if the line contains @done or @canceled
unless contents =~ /\s@(done|cancell?ed)/
remind_match = contents.match(/([^\s"`'\(\[])remind\((.*?)(\s"(.*?)")?\)/)
list_match = contents.match(/([^\s"`'\(\[])list\((.*?)(\s"(.*?)")?\)/)
unless list_match.nil?
@list_name = list_match[2]
end
unless remind_match.nil?
remind_date = Time.parse(remind_match[2])
#Remind only if is still something that will happen.
if remind_date >= Time.now
# This strips the @remind tag from the reminder title or body
remind_stripped_line = contents.gsub(/["`'\(\[]?#{Regexp.escape(remind_match[0])}["`'\)\]]?\s*/, '').gsub(/<\/?string>/, '').strip
# If you don't want to include the reminder list in your note, uncomment the code bellow
unless list_match.nil?
remind_stripped_line = remind_stripped_line.gsub(/["`'\(\[]?#{Regexp.escape(list_match[0])}["`'\)\]]?\s*/, '').gsub(/<\/?string>/, '').strip
end
# remove leading - or * in case it's in a TaskPaper or Markdown list
remind_stripped_line.sub!(/^[\-\*\+] /, '')
filename = "#{notes_dir}/#{file}".gsub(/\+/, '%20')
is_day_one = File.extname(file) =~ /doentry$/
if is_day_one
xml = IO.read(file)
dayone_content = xml.match(/<key>Entry Text<\/key>\s*<string>(.*?)<\/string>/m)[1]
if dayone_content
note_title = dayone_content.split(/\n/)[0].gsub(/[#<>\-\*\+]/, '')[0..30].strip
else
note_title = 'From Day One'
end
else
note_title = File.basename(file).gsub(/\.(txt|md|taskpaper|ft|doentry)$/, '')
end
if remind_stripped_line == ''
@remind_title = remind_match[4] || note_title
@remind_extension = File.extname(file)
@remind_message = "#{@remind_title} [#{remind_date.strftime('%F')}]"
@remind_note = is_day_one ? dayone_content : IO.read(file)
if @extension =~ /(md|txt)$/
# @note += "\n\n- <nvalt://find/#{CGI.escape(note_title).gsub(/\+/,"%20")}>\n"
end
else
@remind_title = remind_match[4] || remind_stripped_line
@remind_extension = ''
@remind_message = "#{@remind_title} [#{remind_date.strftime('%F')}]"
@remind_taskDate = remind_match[2]
# add :#{counter} after #{filename} to include line number below
if is_day_one
@remind_note = stripped_line
else
# @note = "#{stripped_line}\n\n- file://#{filename}\n- nvalt://find/#{CGI.escape(note_title).gsub(/\+/,"%20")}\n"
@remind_note = "#{remind_stripped_line}\n- file://#{filename}\n"
end
end
if @options.verbose
puts "Title: #{@remind_title}"
puts "Extension: #{@remind_extension}"
puts "Message: #{@remind_message}"
puts "Note: #{@remind_note}"
end
notify_remind
if @options.remove
print 'REMOVED'
contents.gsub!(/([^\s"`'\(\[])remind\((.*?)(\s"(.*?)")?\)/) do |match|
if Time.parse(Regexp.last_match(2)) > Time.now
"#{Regexp.last_match(1)}reminded(#{Time.now.strftime('%Y-%m-%d %H:%M')}#{Regexp.last_match(3)})"
else
match
end
end
end
end
end
end
unless contents =~ /\s@(done|cancell?ed)/
event_match = contents.match(/([^\s"`'\(\[])event\((.*?)(\s"(.*?)")?\)/)
event_title_pat = contents.match(/((?<=^..).)(.+?(?=\@))/)
# event_title_pat.sub(/((?<=^..).*$)/)
calendar_match = contents.match(/([^\s"`'\(\[])calendar\((.*?)(\s"(.*?)")?\)/)
duration_match = contents.match(/([^\s"`'\(\[])duration\((.*?)(\s"(.*?)")?\)/)
unless calendar_match.nil?
@calendar_name = calendar_match[2]
end
# duration start
unless duration_match.nil?
@duration = duration_match[2]
end
unless event_match.nil?
event_date = Time.parse(event_match[2])
if event_date >= Time.now
event_stripped_line = contents.gsub(/["`'\(\[]?#{Regexp.escape(event_match[0])}["`'\)\]]?\s*/, '').gsub(/<\/?string>/, '').strip
# If you don't want to include the calendar name in your event, uncomment the following code
unless calendar_match.nil?
event_stripped_line = contents.gsub(/["`'\(\[]?#{Regexp.escape(calendar_match[0])}["`'\)\]]?\s*/, '').gsub(/<\/?string>/, '').strip
end
# remove leading - or * in case it's in a TaskPaper or Markdown list
event_stripped_line.sub!(/^[\-\*\+] /, '')
filename = "#{notes_dir}/#{file}".gsub(/\+/, '%20')
is_day_one = File.extname(file) =~ /doentry$/
if is_day_one
else
event_note_title = File.basename(file).gsub(/\.(txt|md|taskpaper|ft|doentry)$/, '')
end
if event_stripped_line ==''
@event_title_pat = event_title_pat[4] || event_note_title
@event_title = event_match[4] || event_note_title
@event_extension = File.extname(file)
@event_message = "#{@event_title} [#{event_date.strftime('%F')}]"
@event_note = is_day_one ? dayone_content : IO.read(file)
if @event_extension =~ /(md|txt)$/
# @note += "\n\n- <nvalt://find/#{CGI.escape(event_note_title).gsub(/\+/,"%20")}>\n"
end
else
@event_title_pat = event_title_pat
@event_title = event_match[4] || event_stripped_line
@event_extension = ''
@event_message = "#{@event_title} [#{event_date.strftime('%F')}]"
@event_taskDate = event_match[2]
# add :#{counter} after #{filename} to include line number below
if is_day_one
@event_note = event_stripped_line
else
# @note = "#{stripped_line}\n\n- file://#{filename}\n- nvalt://find/#{CGI.escape(event_note_title).gsub(/\+/,"%20")}\n"
@event_note = "#{event_stripped_line}\n- file://#{filename}\n"
end
end
if @options.verbose
puts "Title: #{@event_title}"
puts "Extension: #{@event_extension}"
puts "Message: #{@event_message}"
puts "Note: #{@event_note}"
end
notify_event
if @options.remove
contents.gsub!(/([^\s"`'\(\[])event\((.*?)(\s"(.*?)")?\)/) do |match|
if Time.parse(Regexp.last_match(2)) > Time.now
"#{Regexp.last_match(1)}event_created(#{Time.now.strftime('%Y-%m-%d %H:%M')}#{Regexp.last_match(3)})"
else
match
end
end
end
end
end
end
contents
end
File.open(file, 'w+') do |f|
f.puts lines.join("\n")
end
if @options.preserve_time
`touch -m "#{file}"`
end
end
end
end
def notify_event
puts @remind_message if @options.stdout
if @options.notify == 'terminal-notifier'
TerminalNotifier.notify(@event_title, title: 'Event')
elsif @options.notify == 'growlnotify'
`growlnotify -m "#{@event_message}" -t "Event" -s`
end
if @options.reminders
`osascript <<'APPLESCRIPT'
tell application "Calendar"
if name of calendars does not contain "#{@calendar_name}" then
set _calendars to item 1 of calendars
else
set _calendars to calendar "#{@calendar_name}"
end if
set textDate to "#{@event_taskDate}"
set duration to "#{@duration}"
set resultDate to the current date
set the year of resultDate to (text 1 thru 4 of textDate)
set the month of resultDate to (text 6 thru 7 of textDate)
set the day of resultDate to (text 9 thru 10 of textDate)
set the time of resultDate to 0
if (length of textDate) > 10 then
set the hours of resultDate to (text 12 thru 13 of textDate)
set the minutes of resultDate to (text 15 thru 16 of textDate)
if (length of textDate) > 16 then
set the seconds of resultDate to (text 18 thru 19 of textDate)
end if
end if
make new event at end of _calendars with properties {summary:"#{@event_title_pat}", start date:resultDate, end date:resultDate +duration * Minutes }
delay 5
quit
end tell
APPLESCRIPT`
end
unless @options.file == false
filename = File.join(@options.file, @title)
File.open(filename, 'w+') do |f|
f.puts @remind_note
end
end
unless @options.email == false
subject = @title
content = @note
if @extension == '.taskpaper'
if File.exist?('/usr/local/bin/multimarkdown')
md = "format: complete\n\n#{TaskPaper.new.tp2md(@note)}"
content = `echo #{Shellwords.escape(md)}|/usr/local/bin/multimarkdown`
end
else
if File.exist?('/usr/local/bin/multimarkdown')
content = `echo #{Shellwords.escape("format: complete\n\n" + @note)}|/usr/local/bin/multimarkdown`
end
end
template = <<ENDTEMPLATE
Subject: #{@title}
From: nvreminder@system.net
MIME-Version: 1.0
Content-Type: text/html;
#{content}
ENDTEMPLATE
@options.email.each do|email|
`echo #{Shellwords.escape(template)}|/usr/sbin/sendmail #{email}`
end
end
end
def notify_remind
puts @remind_message if @options.stdout
if @options.notify == 'terminal-notifier'
TerminalNotifier.notify(@remind_message, title: 'Remind')
elsif @options.notify == 'growlnotify'
`growlnotify -m "#{@remind_message}" -t "Reminder" -s`
end
if @options.reminders
`osascript <<'APPLESCRIPT'
tell application "Reminders"
if name of lists does not contain "#{@list_name}" then
set _reminders to item 1 of lists
else
set _reminders to list "#{@list_name}"
end if
set textDate to "#{@remind_taskDate}"
set resultDate to the current date
set the year of resultDate to (text 1 thru 4 of textDate)
set the month of resultDate to (text 6 thru 7 of textDate)
set the day of resultDate to (text 9 thru 10 of textDate)
set the time of resultDate to 0
if (length of textDate) > 10 then
set the hours of resultDate to (text 12 thru 13 of textDate)
set the minutes of resultDate to (text 15 thru 16 of textDate)
if (length of textDate) > 16 then
set the seconds of resultDate to (text 18 thru 19 of textDate)
end if
end if
make new reminder at end of _reminders with properties {name:"#{@remind_title}", remind me date:resultDate}
delay 5
quit
end tell
APPLESCRIPT`
end
unless @options.file == false
filename = File.join(@options.file, @title)
File.open(filename, 'w+') do |f|
f.puts @remind_note
end
end
unless @options.email == false
subject = @title
content = @note
if @extension == '.taskpaper'
if File.exist?('/usr/local/bin/multimarkdown')
md = "format: complete\n\n#{TaskPaper.new.tp2md(@note)}"
content = `echo #{Shellwords.escape(md)}|/usr/local/bin/multimarkdown`
end
else
if File.exist?('/usr/local/bin/multimarkdown')
content = `echo #{Shellwords.escape("format: complete\n\n" + @note)}|/usr/local/bin/multimarkdown`
end
end
template = <<ENDTEMPLATE
Subject: #{@title}
From: nvreminder@system.net
MIME-Version: 1.0
Content-Type: text/html;
#{content}
ENDTEMPLATE
@options.email.each do|email|
`echo #{Shellwords.escape(template)}|/usr/sbin/sendmail #{email}`
end
end
end
end
r = Reminder.new(ARGV)
r.run
Hi Guys, its a old post, but still like the idea of adding reminders from text notes. I have tried it get an error
`/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/time.rb:194:in `make_time': no time information in "" (ArgumentError)`
i have used @remind (2021-08-18 18:00) but same error
any suggestions or any alternatives to achieve this
You may have seen this, but if you type the letters rem
in the TaskPaper Help menu search field, you will find various elements of built-in automation for interactions with Reminders now:
Thank you for your reply. I was hoping to use text files. I use windows for work, i wud have created short notes in text file, synced with One Drive for this Script to create reminders
I know its really not a taskpaper Question