Coder Social home page Coder Social logo

gcalcron's People

Contributors

danestreeter avatar ndbroadbent avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gcalcron's Issues

CANCELED tasks are deleted, but then re-added

With DEBUG = True:

# python /home/ndbroadbent/src/python/GCalCron2/gcalcron2.py
Setting up query: 2012-01-08 21:24:02.781849 to 2012-01-15 21:23:00 modified after 2012-01-08 21:23:00
Setting up query: 2012-01-15 21:23:00 to 2012-01-15 21:24:02.781849 modified after None
Submitting query
Query results received
http://www.google.com/calendar/feeds/[email protected]/private/full/sf20hslp7r3jtmkgllm3r0tcl4 - CANCELED - 2012-01-08T13:23:49.000Z :  Do this thing 2012-01-12 10:00:00  ( 2012-01-12T02:00:00.000Z )  => dagsdafsfda
[{'commands': [{'exec_time': datetime.datetime(2012, 1, 12, 10, 0), 'command': 'dagsdafsfda'}], 'uid': 'http://www.google.com/calendar/feeds/[email protected]/private/full/sf20hslp7r3jtmkgllm3r0tcl4'}]
at -d 287
at 10:00 Jan 12
  warning: commands will be executed using /bin/sh
job 288 at Thu Jan 12 10:00:00 2012

So it removes the cancelled event (at -d 287), but re-adds it again (at 10:00 Jan 12)

No such file or directory: .gcalcron2

Hi,

Awesome package! Love the idea.

I tried following your instructions in the readme.md but when I run the script - it looks for a file not created yet. Also, not sure where to give it the calendar ID.

Cheers,
Noah

off-topic: How can we automate disabling events during a holiday?

Here's another thing that I have been thinking about for a long time. When I'm on holiday, I would love to be able to simply add the event to my 'Holidays' calendar, and have all of my alarms and work-related automation tasks automatically disabled.

So, for example, my home automation system interacts with XBMC, and turns on music in the morning. I also automate a WakeOnLan packet to my computers at the office to turn them on in the morning. I'd also like to automate the heaters and air-conditioning, so that I don't waste energy during the day while I'm at work.

So instead of manually cancelling all these events, it would be great if all we had to do was create a 'holiday' event that automatically disabled them. I already subscribe to a calendar of public holidays, so it would amazing if my alarm automatically turned itself off when I don't need to go to work. You mentioned that a cancelled task woke up your sister during a holiday, so I guess you know what I mean...

I don't think Google Calendar itself can handle these kinds of rules, but please let me know if it can.

I would love to also write a translation layer between the 'at' command, and the alarm app of my (jailbroken) iPhone, so that I can use GCalCron2 to manage my iPhone alarms. (I don't want to use the default calendar alerts because they're too quiet, can't customize ringtone, don't have snooze, etc.)

So, you can see why I'm excited about this feature :)

Maybe this idea is out of the scope of GCalCron2, but I was wondering what you think?

If your commands will not run...

Found this script ideal for my Raspberry Pi. However, with a default install of Raspbian, i could get the script to download events (as was evident in the log) but the commands would not execute at the scheduled time.

The reason is pretty simple; out of the box, the unix tool "at" is not installed.

To do this, simply run sudo apt-get install at

That should do the trick. I spent a good few hours trying to work this out.

Query/Parsing Issue?

I have a bunch of pis running this that have been working great for years. I just tried setting up a new one and now it doesn't seem like its working any more. Its creating the first entry each time the gcalcron.py gets run even though its a duplicate. I am running the modified version by Mestalbet for python 3, but it had been working fine for months, but now it seems like its not, even on pis that haven't been updated.. The old pis running the python2 version are still ok I think. I can't run the python2 version on the pis anymore since its all been depreciated and removed. Any thoughts?

atq

38	Wed Jul  8 14:00:00 2020 a pi
37	Wed Jul  8 14:00:00 2020 a pi
36	Wed Jul  8 14:00:00 2020 a pi


cat gcalcron/gcalcron.log 

Setting up query: 2020-07-08T11:19:23.791570-06:00 to 2020-07-15T11:19:23.791570-06:00 modified after None
Submitting query
Query results received
[{'id': '5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z', 'status': 'confirmed', 'updated': '2020-07-08T16:39:38.126Z', 'summary': 'Test', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-08T14:00:00-06:00', 'timeZone': 'America/Denver'}, 'end': {'dateTime': '2020-07-08T15:00:00-06:00', 'timeZone': 'America/Denver'}}, {'id': '4pavvn9uosk73i9ok8vf51m4kn', 'status': 'confirmed', 'updated': '2020-07-08T17:17:44.748Z', 'summary': 'test2', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-09T14:30:00-06:00'}, 'end': {'dateTime': '2020-07-09T15:30:00-06:00'}}, {'id': '2lnan2bkthuj9a1jdongv0gtb6', 'status': 'confirmed', 'updated': '2020-07-08T17:17:54.755Z', 'summary': 'Test3', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-10T12:30:00-06:00'}, 'end': {'dateTime': '2020-07-10T13:30:00-06:00'}}]
5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z-confirmed-2020-07-08T16:39:38.126Z: 2020-07-08 14:00:00 -> 2020-07-08 15:00:00 (2020-07-08T14:00:00-06:00 -> 2020-07-08T15:00:00-06:00) =>/home/pi/tvoff.sh
4pavvn9uosk73i9ok8vf51m4kn-confirmed-2020-07-08T17:17:44.748Z: 2020-07-09 14:30:00 -> 2020-07-09 15:30:00 (2020-07-09T14:30:00-06:00 -> 2020-07-09T15:30:00-06:00) =>/home/pi/tvoff.sh
2lnan2bkthuj9a1jdongv0gtb6-confirmed-2020-07-08T17:17:54.755Z: 2020-07-10 12:30:00 -> 2020-07-10 13:30:00 (2020-07-10T12:30:00-06:00 -> 2020-07-10T13:30:00-06:00) =>/home/pi/tvoff.sh
[{'uid': '5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 8, 14, 0)}]}, {'uid': '4pavvn9uosk73i9ok8vf51m4kn', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 9, 14, 30)}]}, {'uid': '2lnan2bkthuj9a1jdongv0gtb6', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 10, 12, 30)}]}]
['at', '14:00 Jul 08']
b''
b'warning: commands will be executed using /bin/sh\njob 36 at Wed Jul  8 14:00:00 2020\n'
Setting up query: 2020-07-08T11:19:27.928713-06:00 to 2020-07-15T11:19:27.928713-06:00 modified after None
Submitting query
Query results received
[{'id': '5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z', 'status': 'confirmed', 'updated': '2020-07-08T16:39:38.126Z', 'summary': 'Test', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-08T14:00:00-06:00', 'timeZone': 'America/Denver'}, 'end': {'dateTime': '2020-07-08T15:00:00-06:00', 'timeZone': 'America/Denver'}}, {'id': '4pavvn9uosk73i9ok8vf51m4kn', 'status': 'confirmed', 'updated': '2020-07-08T17:17:44.748Z', 'summary': 'test2', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-09T14:30:00-06:00'}, 'end': {'dateTime': '2020-07-09T15:30:00-06:00'}}, {'id': '2lnan2bkthuj9a1jdongv0gtb6', 'status': 'confirmed', 'updated': '2020-07-08T17:17:54.755Z', 'summary': 'Test3', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-10T12:30:00-06:00'}, 'end': {'dateTime': '2020-07-10T13:30:00-06:00'}}]
5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z-confirmed-2020-07-08T16:39:38.126Z: 2020-07-08 14:00:00 -> 2020-07-08 15:00:00 (2020-07-08T14:00:00-06:00 -> 2020-07-08T15:00:00-06:00) =>/home/pi/tvoff.sh
4pavvn9uosk73i9ok8vf51m4kn-confirmed-2020-07-08T17:17:44.748Z: 2020-07-09 14:30:00 -> 2020-07-09 15:30:00 (2020-07-09T14:30:00-06:00 -> 2020-07-09T15:30:00-06:00) =>/home/pi/tvoff.sh
2lnan2bkthuj9a1jdongv0gtb6-confirmed-2020-07-08T17:17:54.755Z: 2020-07-10 12:30:00 -> 2020-07-10 13:30:00 (2020-07-10T12:30:00-06:00 -> 2020-07-10T13:30:00-06:00) =>/home/pi/tvoff.sh
[{'uid': '5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 8, 14, 0)}]}, {'uid': '4pavvn9uosk73i9ok8vf51m4kn', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 9, 14, 30)}]}, {'uid': '2lnan2bkthuj9a1jdongv0gtb6', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 10, 12, 30)}]}]
['at', '14:00 Jul 08']
b''
b'warning: commands will be executed using /bin/sh\njob 37 at Wed Jul  8 14:00:00 2020\n'
Setting up query: 2020-07-08T11:19:32.441675-06:00 to 2020-07-15T11:19:32.441675-06:00 modified after None
Submitting query
Query results received
[{'id': '5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z', 'status': 'confirmed', 'updated': '2020-07-08T16:39:38.126Z', 'summary': 'Test', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-08T14:00:00-06:00', 'timeZone': 'America/Denver'}, 'end': {'dateTime': '2020-07-08T15:00:00-06:00', 'timeZone': 'America/Denver'}}, {'id': '4pavvn9uosk73i9ok8vf51m4kn', 'status': 'confirmed', 'updated': '2020-07-08T17:17:44.748Z', 'summary': 'test2', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-09T14:30:00-06:00'}, 'end': {'dateTime': '2020-07-09T15:30:00-06:00'}}, {'id': '2lnan2bkthuj9a1jdongv0gtb6', 'status': 'confirmed', 'updated': '2020-07-08T17:17:54.755Z', 'summary': 'Test3', 'description': '/home/pi/tvoff.sh', 'start': {'dateTime': '2020-07-10T12:30:00-06:00'}, 'end': {'dateTime': '2020-07-10T13:30:00-06:00'}}]
5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z-confirmed-2020-07-08T16:39:38.126Z: 2020-07-08 14:00:00 -> 2020-07-08 15:00:00 (2020-07-08T14:00:00-06:00 -> 2020-07-08T15:00:00-06:00) =>/home/pi/tvoff.sh
4pavvn9uosk73i9ok8vf51m4kn-confirmed-2020-07-08T17:17:44.748Z: 2020-07-09 14:30:00 -> 2020-07-09 15:30:00 (2020-07-09T14:30:00-06:00 -> 2020-07-09T15:30:00-06:00) =>/home/pi/tvoff.sh
2lnan2bkthuj9a1jdongv0gtb6-confirmed-2020-07-08T17:17:54.755Z: 2020-07-10 12:30:00 -> 2020-07-10 13:30:00 (2020-07-10T12:30:00-06:00 -> 2020-07-10T13:30:00-06:00) =>/home/pi/tvoff.sh
[{'uid': '5e2fnn6ge1d4dsav9692o2i0gg_20200708T200000Z', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 8, 14, 0)}]}, {'uid': '4pavvn9uosk73i9ok8vf51m4kn', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 9, 14, 30)}]}, {'uid': '2lnan2bkthuj9a1jdongv0gtb6', 'commands': [{'command': '/home/pi/tvoff.sh', 'exec_time': datetime.datetime(2020, 7, 10, 12, 30)}]}]
['at', '14:00 Jul 08']
b''
b'warning: commands will be executed using /bin/sh\njob 38 at Wed Jul  8 14:00:00 2020\n'

404 not found - placing client email in double quotes causing this?, single quotes causes cannot be decoded..

pi@RUBY ~/gcalcron $ python gcalcron.py
ERROR:root:Sync failed
Traceback (most recent call last):
File "gcalcron.py", line 420, in main
g.sync_gcal_to_cron()
File "gcalcron.py", line 296, in sync_gcal_to_cron
events = self.gCalAdapter.get_events(sync_start, last_sync, num_days)
File "gcalcron.py", line 166, in get_events
return self.queryApi(queries)
File "gcalcron.py", line 134, in queryApi
gCalEvents = self.service.events().list(*_query).execute()
File "/usr/local/lib/python2.7/dist-packages/oauth2client/util.py", line 132, in positional_wrapper
return wrapped(_args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/apiclient/http.py", line 723, in execute
raise HttpError(resp, content, uri=self.uri)
HttpError: <HttpError 404 when requesting https://www.googleapis.com/calendar/v3/calendars/%[email protected]/events?orderBy=updated&timeMax=2014-01-22T22%3A27%3A31.942439%2B00%3A00&fields=items%28description%2Cend%2Cid%2Cstart%2Cstatus%2Csummary%2Cupdated%29&singleEvents=true&maxResults=1000&showDeleted=true&timeMin=2014-01-15T22%3A27%3A31.942439%2B00%3A00&alt=json returned "Not Found">

OSError: [Errno 2] No such file or directory

ERROR:root:Sync failed
Traceback (most recent call last):
File "./gcalcron2.py", line 427, in main
g.sync_gcal_to_cron()
File "./gcalcron2.py", line 306, in sync_gcal_to_cron
self.schedule_new_jobs(commandsList)
File "./gcalcron2.py", line 262, in schedule_new_jobs
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
File "/usr/lib/python2.7/subprocess.py", line 679, in init
errread, errwrite)
File "/usr/lib/python2.7/subprocess.py", line 1259, in _execute_child
raise child_exception
OSError: [Errno 2] No such file or directory

Rpi python 2.7.3 on raspian

any idea on whats wrong ?

parse_events issue

Any idea why it won't work for me, I use Python 2.7 and I believe I have all the required libraires:
python -m doctest -v gcalcron.py
Trying:
g = GCalAdapter()
Expecting nothing
ok
Trying:
g.get_query(datetime.datetime(2011, 6, 19, 14, 0), datetime.datetime(2011, 6, 26, 14, 0), datetime.datetime(2011, 6, 18, 14, 0))
Expecting:
{'orderBy': 'updated', 'showDeleted': True, 'calendarId': None, 'timeMin': '2011-06-19T14:00:00', 'updatedMin': '2011-06-18T14:00:00', 'timeMax': '2011-06-26T14:00:00', 'fields': 'items(description,end,id,start,status,summary,updated)', 'singleEvents': True, 'maxResults': 1000}
ok
Trying:
datetime_to_at(datetime.datetime(2011, 6, 18, 12, 0))
Expecting:
'12:00 Jun 18'
ok
Trying:
parse_commands("echo 'Wake up!'\n+10: echo 'Wake up, you are 10 minutes late!'", datetime.datetime(3011, 6, 19, 8, 30), datetime.datetime(3011, 6, 19, 9, 0))
Expecting:
[{'exec_time': datetime.datetime(3011, 6, 19, 8, 30), 'command': "echo 'Wake up!'"}, {'exec_time': datetime.datetime(3011, 6, 19, 8, 40), 'command': "echo 'Wake up, you are 10 minutes late!'"}]
ok
Trying:
parse_commands("Turn on lights\nend -10: Dim lights\nend: Turn off lights", datetime.datetime(3011, 6, 19, 18, 30), datetime.datetime(3011, 6, 19, 23, 0))
Expecting:
[{'exec_time': datetime.datetime(3011, 6, 19, 18, 30), 'command': 'Turn on lights'}, {'exec_time': datetime.datetime(3011, 6, 19, 22, 50), 'command': 'Dim lights'}, {'exec_time': datetime.datetime(3011, 6, 19, 23, 0), 'command': 'Turn off lights'}]
ok
Trying:
parse_events([{u'status': u'confirmed', u'updated': u'2013-12-22T19:49:13.750Z', u'end': {u'dateTime': u'2013-12-23T02:00:00+01:00'}, u'description': u'-60: start_heating.py\n0: turn_music_on.py\n+30: stop_heating.py', u'summary': u'Wakeup', u'start': {u'dateTime': u'2013-12-23T01:00:00+01:00'}, u'id': u'olbia2urfm1ns0h88v4u0d9a5g'}])
Expecting:
[{'commands': [{'exec_time': datetime.datetime(2013, 12, 23, 0, 0), 'command': u'start_heating.py'}, {'exec_time': datetime.datetime(2013, 12, 23, 1, 0), 'command': u'0: turn_music_on.py'}, {'exec_time': datetime.datetime(2013, 12, 23, 1, 30), 'command': u'stop_heating.py'}], 'uid': u'olbia2urfm1ns0h88v4u0d9a5g'}]


File "gcalcron.py", line 358, in gcalcron.parse_events
Failed example:
parse_events([{u'status': u'confirmed', u'updated': u'2013-12-22T19:49:13.750Z', u'end': {u'dateTime': u'2013-12-23T02:00:00+01:00'}, u'description': u'-60: start_heating.py\n0: turn_music_on.py\n+30: stop_heating.py', u'summary': u'Wakeup', u'start': {u'dateTime': u'2013-12-23T01:00:00+01:00'}, u'id': u'olbia2urfm1ns0h88v4u0d9a5g'}])
Expected:
[{'commands': [{'exec_time': datetime.datetime(2013, 12, 23, 0, 0), 'command': u'start_heating.py'}, {'exec_time': datetime.datetime(2013, 12, 23, 1, 0), 'command': u'0: turn_music_on.py'}, {'exec_time': datetime.datetime(2013, 12, 23, 1, 30), 'command': u'stop_heating.py'}], 'uid': u'olbia2urfm1ns0h88v4u0d9a5g'}]
Got:
[]
Trying:
parse_events([{u'status': u'cancelled', u'updated': u'2013-12-22T19:52:50.525Z', u'end': {u'dateTime': u'2013-12-23T02:00:00+01:00'}, u'description': u'-60: start_heating.py\n0: turn_music_on.py\n+30: stop_heating.py', u'summary': u'Wakeup', u'start': {u'dateTime': u'2013-12-23T01:00:00+01:00'}, u'id': u'olbia2urfm1ns0h88v4u0d9a5g'}])
Expecting:
[{'uid': u'olbia2urfm1ns0h88v4u0d9a5g'}]
ok
17 items had no tests:
gcalcron
gcalcron.GCalAdapter
gcalcron.GCalAdapter.init
gcalcron.GCalAdapter.get_events
gcalcron.GCalAdapter.queryApi
gcalcron.GCalCron
gcalcron.GCalCron.init
gcalcron.GCalCron.clean_settings
gcalcron.GCalCron.getCalendarId
gcalcron.GCalCron.init_settings
gcalcron.GCalCron.load_settings
gcalcron.GCalCron.reset_settings
gcalcron.GCalCron.save_settings
gcalcron.GCalCron.schedule_new_jobs
gcalcron.GCalCron.sync_gcal_to_cron
gcalcron.GCalCron.unschedule_old_jobs
gcalcron.main
3 items passed all tests:
2 tests in gcalcron.GCalAdapter.get_query
1 tests in gcalcron.datetime_to_at
2 tests in gcalcron.parse_commands


1 items had failures:
1 of 2 in gcalcron.parse_events
7 tests in 21 items.
6 passed and 1 failed.
_Test Failed_ 1 failures.
root@ubuntu:~/gcalcron# python gcalcron.py
ERROR:root:Sync failed
Traceback (most recent call last):
File "gcalcron.py", line 420, in main
g.sync_gcal_to_cron()
File "gcalcron.py", line 297, in sync_gcal_to_cron
commandsList = parse_events(events)
File "gcalcron.py", line 371, in parse_events
logger.debug(event['id'] + '-' + event['status'] + '-' + event['updated'] + ': ' + unicode(start_time) + ' -> ' + unicode(end_time) + ' (' + event['start']['dateTime'] + ' -> ' + event['end']['dateTime'] + ') ' + '=>' + event['description'])
KeyError: 'description'

Crashes when processing a job that's scheduled for the past

First, thanks so much for creating this! You have done an awesome job, and it works almost perfectly! However, I managed to find a small bug:

  • Set a calendar event for 8:30 pm, with the line:
-10: DISPLAY=:0 notify-send "The task will run in 10 minutes"
  • Run python gcalcron2.py at 8:25 pm.

The script crashes like this:

Traceback (most recent call last):
  File "/home/ndbroadbent/src/python/GCalCron2/gcalcron2.py", line 297, in <module>
    g.sync_gcal_to_cron()
  File "/home/ndbroadbent/src/python/GCalCron2/gcalcron2.py", line 246, in sync_gcal_to_cron
    job_id = re.compile('job (\d+) at').search(output).group(1)
AttributeError: 'NoneType' object has no attribute 'group'

Thanks again for your sharing your work!
I can't wait to use this for my home automation schedule, as well as Wake-on-Lan & shutdown for my PCs.

flags not defined

root@RUBY:/gcalcron# python gcalcron.py
ERROR:root:Sync failed
Traceback (most recent call last):
File "gcalcron.py", line 427, in main
g.sync_gcal_to_cron()
File "gcalcron.py", line 300, in sync_gcal_to_cron
events = self.gCalAdapter.get_events(sync_start, last_sync, num_days)
File "gcalcron.py", line 170, in get_events
return self.queryApi(queries)
File "gcalcron.py", line 137, in queryApi
gCalEvents = self.get_service().events().list(**query).execute()
File "gcalcron.py", line 84, in get_service
credentials = tools.run_flow(FLOW, storage, Flags)
NameError: global name 'flags' is not defined
root@RUBY:
/gcalcron#

I have had this issue as-well as the initial last_sync being null and throwing out allot of errors, I placed yesterdays date in for a initial run and received this

support for 'macros'

Here's an idea for a feature that I think would be really useful. It's also something that I would be very happy to write, but I wanted to first hear your thoughts.
I currently have a task that shuts down my desktop computer at 10:30pm on workdays, otherwise I tend to stay up all night working on projects like this...
So in the description of the event, I have the following lines:

-10: DISPLAY=:0 notify-send "Shutting down computer in 10 minutes"
-5: DISPLAY=:0 notify-send "Shutting down computer in 5 minutes"
-3: DISPLAY=:0 notify-send "Shutting down computer in 3 minutes" "Please save your work"
-1: DISPLAY=:0 notify-send Shutting down computer in 1 minute" "Please save your work"
poweroff

I would like to replace this with something like:

macro: shutdown

This would cause the script to search a macros folder in the application directory, or maybe the users HOME directory, for a file called shutdown. It would then simply replace that line in the description with the contents of the macro file, and parse the final result.
This means that macros could be combined with commands, or even other macros, to create complex tasks.

What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.