-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhancement: import agenda from CSV #550
Comments
@jcrowgey Could you let me know what you think of this design? |
Overall this looks good to me. Your recent work on this project has
been very helpful and appreciated. I'm sorry for the long delays. In
general, I'm not going to be able to work on gcalcli much for the
foreseeable future.
…On Mon, Jul 27, 2020 at 03:15:57PM -0700, Michael Hoffman wrote:
@jcrowgey Could you let me know what you think of this design?
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#550 (comment)
|
Refactor `gcalcli.gcal.GoogleCalendarInterface._tsv()`, replacing the long list of if statements with a modular design using a series of `gcalcli.details.Handler` classes. Each `Handler` represents the set of event properties that are captured by each item of `gcalcli.argparsers.DETAILS`. So far only the `header` class attribute and the `get()` methods are implemented. This is what is necessary for TSV output. Preparatory work for insanum#550.
Set `gcalcli.argparsers.DETAILS` to a superset of `gcalcli.details.HANDLERS.keys()` to avoid duplication. Preparatory work for insanum#550.
BREAKING CHANGE: having a header row will break any software that assumes that the output of `gcalcli agenda --tsv` doesn't have one. Also slightly refactored the output printing to use print() like in most parts of the code. Partially implements insanum#550.
Partially implements insanum#550.
argparsers.py, cli.py: add `agendaupdate` subcommand gcal.py: add `AgendaUpdate()` details.py: - `Title`, `SimpleSingleFieldHandler`: add `patch()` - add `FIELD_HANDLERS` Minimum viable product for insanum#550.
This will enable `Handler`s with multiple fields to work for patching. Necessary for `Time.patch()` and `Conference.patch()` Also introduce `SingleFieldHandler.patch(cls,` ...`)` which dispatches to `cls._patch()`. Preparatory for further implementation of insanum#550.
Round trips don't work for all day events. The current output format is to present them as events starting and ending at midnight of the same day. But this keeps us from having events starting and ending at midnight of the same day. In the next commit I plan to change the output for all day events so the time fields are blank. Also: - change signature of `Handler.patch()` to include `cal`, which is necessary to extract the timezone - change default of `Handler.fieldnames` from `None` to `[]` to silence a mypy warning. Partially implements insanum#550.
To get the TSV output of |
The easiest way to use a UTF-16 PYTHONIOENCODING=utf_16 gcalcli agendaupdate < file.tsv |
The output of gcalcli agenda | grep Title | iconv --to-code=utf-16 > file.csv |
Partially implements insanum#522 and insanum#550.
This treats calendar as a readonly field. Actually moving calendar would be a bit more involved, and would necessitate: - changing agendaupdate from its current one-calendar-only restriction - using the events().move() method to do the move. It's not done by events().patch() That can be a future feature. Partially implements insanum#550.
* feat: add agendaupdate for calendar This treats calendar as a readonly field. Actually moving calendar would be a bit more involved, and would necessitate: - changing agendaupdate from its current one-calendar-only restriction - using the events().move() method to do the move. It's not done by events().patch() That can be a future feature. Partially implements #550. * feat!: add header row for `gcalcli agenda --tsv` BREAKING CHANGE: having a header row will break any software that assumes that the output of `gcalcli agenda --tsv` doesn't have one. Also slightly refactored the output printing to use print() like in most parts of the code. * feat!: gcal agenda --tsv prints empty time fields for all-day events BREAKING CHANGE: this changes the previous behavior which was to depict all-day events as starting and ending at midnight. * feat: add `--detail id` for `gcalcli agenda --tsv` * feat: add agendaupdate for title, id, location, description argparsers.py, cli.py: add `agendaupdate` subcommand gcal.py: add `AgendaUpdate()` details.py: - `Title`, `SimpleSingleFieldHandler`: add `patch()` - add `FIELD_HANDLERS` * feat: add agendaupdate for time Round trips don't work for all day events. The current output format is to present them as events starting and ending at midnight of the same day. But this keeps us from having events starting and ending at midnight of the same day. Also: - change signature of `Handler.patch()` to include `cal`, which is necessary to extract the timezone - change default of `Handler.fieldnames` from `None` to `[]` to silence a mypy warning. * feat: add agendaupdate for url Both these fields are read-only. If html_link is in the input, always fail. If hangout_link is in the input, check against the current value. If they don't match, fail. * feat: add agendaupdate for conference Partially implements #522. * refactor: move _tsv() implementation to gcalcli.details Refactor `gcalcli.gcal.GoogleCalendarInterface._tsv()`, replacing the long list of if statements with a modular design using a series of `gcalcli.details.Handler` classes. Each `Handler` represents the set of event properties that are captured by each item of `gcalcli.argparsers.DETAILS`. So far only the `header` class attribute and the `get()` methods are implemented. This is what is necessary for TSV output. * fix: change SimpleSingleColumnHandler.header to a simple list Can't use a property on a classmethod without making a custom metaclass. Without that, the property is only enacted upon class instantiation, which we aren't doing here. A custom metaclass is too magic, and thus far we have avoided instantiating what would be singleton objects. Also changed `SimpleSingleColumnHandler._get()` to use `cls.header[0]` instead of `cls._key` which is removed. * ci: eliminate flake8-docstrings method docstring checks for details.py These are mostly overriding pure virtual classes and adding a docstring to each one would be superfluous and make the code harder to read. * refactor: derive `argparsers.DETAILS` from `details.HANDLERS` Set `gcalcli.argparsers.DETAILS` to a superset of `gcalcli.details.HANDLERS.keys()` to avoid duplication. * refactor: change `header` to `fieldnames` and `column` to `field` * refactor: change `Handler.patch()` to include `fieldname` argument This will enable `Handler`s with multiple fields to work for patching. Necessary for `Time.patch()` and `Conference.patch()` Also introduce `SingleFieldHandler.patch(cls,` ...`)` which dispatches to `cls._patch()`. * refactor: move gcal.GoogleCalendarInterface._isallday() to utils.is_all_day() This enables its use by details.py. * refactor: `details.Url` uses `details.URL_PROPS` to identify properties to get The new `details.URL_PROPS` constant `OrderedDict` will also work for use in `Url.patch()`, which should be implemented in the next commit. This will allow automated handling of two property/fieldname pairs for this `Handler`. * refactor: add `exceptions.ReadonlyError`, `exceptions.ReadonlyCheckError`
Implement #550. Includes BREAKING CHANGES. Perhaps next release should increase major version number. * feat!: add header row for `gcalcli agenda --tsv` BREAKING CHANGE: having a header row will break any software that assumes that the output of `gcalcli agenda --tsv` doesn't have one. Also slightly refactored the output printing to use print() like in most parts of the code. * feat!: gcal agenda --tsv prints empty time fields for all-day events BREAKING CHANGE: this changes the previous behavior which was to depict all-day events as starting and ending at midnight. * feat: add `--detail id` for `gcalcli agenda --tsv` * feat: add agendaupdate for title, id, location, description argparsers.py, cli.py: add `agendaupdate` subcommand gcal.py: add `AgendaUpdate()` details.py: - `Title`, `SimpleSingleFieldHandler`: add `patch()` - add `FIELD_HANDLERS` * feat: add agendaupdate for time Round trips don't work for all day events. The current output format is to present them as events starting and ending at midnight of the same day. But this keeps us from having events starting and ending at midnight of the same day. In the next commit I plan to change the output for all day events so the time fields are blank. Also: - change signature of `Handler.patch()` to include `cal`, which is necessary to extract the timezone - change default of `Handler.fieldnames` from `None` to `[]` to silence a mypy warning. * feat: add detail for `action` for `gcalcli agenda`. Column is ignored by `gcalcli agendaupdate`. * feat: add agendaupdate action dispatch and `patch` action Refactor most of existing agendaupdate into new module `gcalcli.actions` in `patch()`. * feat: add agendaupdate for calendar This treats calendar as a readonly field. Actually moving calendar would be a bit more involved, and would necessitate: - changing agendaupdate from its current one-calendar-only restriction - using the events().move() method to do the move. It's not done by events().patch() That can be a future feature. * feat: add agendaupdate `ignore` action * feat: add agendaupdate `delete` action * feat: add agendaupdate `insert` action * feat: `agendaupdate` patch action uses insert if no id supplied * feat: error when unsupported agendaupdate action used Also limits actions to a defined set. Before one could have specified action to be name of some other object in `gcalcli.actions` with unpredictable results. * feat: add agendaupdate for url Both these fields are read-only. If html_link is in the input, always fail. If hangout_link is in the input, check against the current value. If they don't match, fail. * feat: add agendaupdate for conference Partially implements #522. * fix: change SimpleSingleColumnHandler.header to a simple list Can't use a property on a classmethod without making a custom metaclass. Without that, the property is only enacted upon class instantiation, which we aren't doing here. A custom metaclass is too magic, and thus far we have avoided instantiating what would be singleton objects. Also changed `SimpleSingleColumnHandler._get()` to use `cls.header[0]` instead of `cls._key` which is removed. * fix: gcalcli agenda to work with refactor 72710b4 * refactor: move _tsv() implementation to gcalcli.details Refactor `gcalcli.gcal.GoogleCalendarInterface._tsv()`, replacing the long list of if statements with a modular design using a series of `gcalcli.details.Handler` classes. Each `Handler` represents the set of event properties that are captured by each item of `gcalcli.argparsers.DETAILS`. So far only the `header` class attribute and the `get()` methods are implemented. This is what is necessary for TSV output. * refactor: derive `argparsers.DETAILS` from `details.HANDLERS` Set `gcalcli.argparsers.DETAILS` to a superset of `gcalcli.details.HANDLERS.keys()` to avoid duplication. * refactor: change `header` to `fieldnames` and `column` to `field` * refactor: change `Handler.patch()` to include `fieldname` argument This will enable `Handler`s with multiple fields to work for patching. Necessary for `Time.patch()` and `Conference.patch()` Also introduce `SingleFieldHandler.patch(cls,` ...`)` which dispatches to `cls._patch()`. * refactor: move gcal.GoogleCalendarInterface._isallday() to utils.is_all_day() This enables its use by details.py. * refactor: `details.Url` uses `details.URL_PROPS` to identify properties to get The new `details.URL_PROPS` constant `OrderedDict` will also work for use in `Url.patch()`, which should be implemented in the next commit. This will allow automated handling of two property/fieldname pairs for this `Handler`. * refactor: add `exceptions.ReadonlyError`, `exceptions.ReadonlyCheckError` * refactor: add `gcal.GoogleCalendarInterface.get_events()` change previous uses of `get_cal_service().events()` to `get_events()` * refactor: add `gcal.GoogleCalendarInterface.delete()` * refactor: add gcalcli.actions._iter_field_handlers() * style: add extra line of whitespace * ci: eliminate flake8-docstrings method docstring checks for details.py These are mostly overriding pure virtual classes and adding a docstring to each one would be superfluous and make the code harder to read.
* feat: add agendaupdate for calendar This treats calendar as a readonly field. Actually moving calendar would be a bit more involved, and would necessitate: - changing agendaupdate from its current one-calendar-only restriction - using the events().move() method to do the move. It's not done by events().patch() That can be a future feature. Partially implements insanum#550. * feat!: add header row for `gcalcli agenda --tsv` BREAKING CHANGE: having a header row will break any software that assumes that the output of `gcalcli agenda --tsv` doesn't have one. Also slightly refactored the output printing to use print() like in most parts of the code. * feat!: gcal agenda --tsv prints empty time fields for all-day events BREAKING CHANGE: this changes the previous behavior which was to depict all-day events as starting and ending at midnight. * feat: add `--detail id` for `gcalcli agenda --tsv` * feat: add agendaupdate for title, id, location, description argparsers.py, cli.py: add `agendaupdate` subcommand gcal.py: add `AgendaUpdate()` details.py: - `Title`, `SimpleSingleFieldHandler`: add `patch()` - add `FIELD_HANDLERS` * feat: add agendaupdate for time Round trips don't work for all day events. The current output format is to present them as events starting and ending at midnight of the same day. But this keeps us from having events starting and ending at midnight of the same day. Also: - change signature of `Handler.patch()` to include `cal`, which is necessary to extract the timezone - change default of `Handler.fieldnames` from `None` to `[]` to silence a mypy warning. * feat: add agendaupdate for url Both these fields are read-only. If html_link is in the input, always fail. If hangout_link is in the input, check against the current value. If they don't match, fail. * feat: add agendaupdate for conference Partially implements insanum#522. * refactor: move _tsv() implementation to gcalcli.details Refactor `gcalcli.gcal.GoogleCalendarInterface._tsv()`, replacing the long list of if statements with a modular design using a series of `gcalcli.details.Handler` classes. Each `Handler` represents the set of event properties that are captured by each item of `gcalcli.argparsers.DETAILS`. So far only the `header` class attribute and the `get()` methods are implemented. This is what is necessary for TSV output. * fix: change SimpleSingleColumnHandler.header to a simple list Can't use a property on a classmethod without making a custom metaclass. Without that, the property is only enacted upon class instantiation, which we aren't doing here. A custom metaclass is too magic, and thus far we have avoided instantiating what would be singleton objects. Also changed `SimpleSingleColumnHandler._get()` to use `cls.header[0]` instead of `cls._key` which is removed. * ci: eliminate flake8-docstrings method docstring checks for details.py These are mostly overriding pure virtual classes and adding a docstring to each one would be superfluous and make the code harder to read. * refactor: derive `argparsers.DETAILS` from `details.HANDLERS` Set `gcalcli.argparsers.DETAILS` to a superset of `gcalcli.details.HANDLERS.keys()` to avoid duplication. * refactor: change `header` to `fieldnames` and `column` to `field` * refactor: change `Handler.patch()` to include `fieldname` argument This will enable `Handler`s with multiple fields to work for patching. Necessary for `Time.patch()` and `Conference.patch()` Also introduce `SingleFieldHandler.patch(cls,` ...`)` which dispatches to `cls._patch()`. * refactor: move gcal.GoogleCalendarInterface._isallday() to utils.is_all_day() This enables its use by details.py. * refactor: `details.Url` uses `details.URL_PROPS` to identify properties to get The new `details.URL_PROPS` constant `OrderedDict` will also work for use in `Url.patch()`, which should be implemented in the next commit. This will allow automated handling of two property/fieldname pairs for this `Handler`. * refactor: add `exceptions.ReadonlyError`, `exceptions.ReadonlyCheckError`
Implement insanum#550. Includes BREAKING CHANGES. Perhaps next release should increase major version number. * feat!: add header row for `gcalcli agenda --tsv` BREAKING CHANGE: having a header row will break any software that assumes that the output of `gcalcli agenda --tsv` doesn't have one. Also slightly refactored the output printing to use print() like in most parts of the code. * feat!: gcal agenda --tsv prints empty time fields for all-day events BREAKING CHANGE: this changes the previous behavior which was to depict all-day events as starting and ending at midnight. * feat: add `--detail id` for `gcalcli agenda --tsv` * feat: add agendaupdate for title, id, location, description argparsers.py, cli.py: add `agendaupdate` subcommand gcal.py: add `AgendaUpdate()` details.py: - `Title`, `SimpleSingleFieldHandler`: add `patch()` - add `FIELD_HANDLERS` * feat: add agendaupdate for time Round trips don't work for all day events. The current output format is to present them as events starting and ending at midnight of the same day. But this keeps us from having events starting and ending at midnight of the same day. In the next commit I plan to change the output for all day events so the time fields are blank. Also: - change signature of `Handler.patch()` to include `cal`, which is necessary to extract the timezone - change default of `Handler.fieldnames` from `None` to `[]` to silence a mypy warning. * feat: add detail for `action` for `gcalcli agenda`. Column is ignored by `gcalcli agendaupdate`. * feat: add agendaupdate action dispatch and `patch` action Refactor most of existing agendaupdate into new module `gcalcli.actions` in `patch()`. * feat: add agendaupdate for calendar This treats calendar as a readonly field. Actually moving calendar would be a bit more involved, and would necessitate: - changing agendaupdate from its current one-calendar-only restriction - using the events().move() method to do the move. It's not done by events().patch() That can be a future feature. * feat: add agendaupdate `ignore` action * feat: add agendaupdate `delete` action * feat: add agendaupdate `insert` action * feat: `agendaupdate` patch action uses insert if no id supplied * feat: error when unsupported agendaupdate action used Also limits actions to a defined set. Before one could have specified action to be name of some other object in `gcalcli.actions` with unpredictable results. * feat: add agendaupdate for url Both these fields are read-only. If html_link is in the input, always fail. If hangout_link is in the input, check against the current value. If they don't match, fail. * feat: add agendaupdate for conference Partially implements insanum#522. * fix: change SimpleSingleColumnHandler.header to a simple list Can't use a property on a classmethod without making a custom metaclass. Without that, the property is only enacted upon class instantiation, which we aren't doing here. A custom metaclass is too magic, and thus far we have avoided instantiating what would be singleton objects. Also changed `SimpleSingleColumnHandler._get()` to use `cls.header[0]` instead of `cls._key` which is removed. * fix: gcalcli agenda to work with refactor 72710b4 * refactor: move _tsv() implementation to gcalcli.details Refactor `gcalcli.gcal.GoogleCalendarInterface._tsv()`, replacing the long list of if statements with a modular design using a series of `gcalcli.details.Handler` classes. Each `Handler` represents the set of event properties that are captured by each item of `gcalcli.argparsers.DETAILS`. So far only the `header` class attribute and the `get()` methods are implemented. This is what is necessary for TSV output. * refactor: derive `argparsers.DETAILS` from `details.HANDLERS` Set `gcalcli.argparsers.DETAILS` to a superset of `gcalcli.details.HANDLERS.keys()` to avoid duplication. * refactor: change `header` to `fieldnames` and `column` to `field` * refactor: change `Handler.patch()` to include `fieldname` argument This will enable `Handler`s with multiple fields to work for patching. Necessary for `Time.patch()` and `Conference.patch()` Also introduce `SingleFieldHandler.patch(cls,` ...`)` which dispatches to `cls._patch()`. * refactor: move gcal.GoogleCalendarInterface._isallday() to utils.is_all_day() This enables its use by details.py. * refactor: `details.Url` uses `details.URL_PROPS` to identify properties to get The new `details.URL_PROPS` constant `OrderedDict` will also work for use in `Url.patch()`, which should be implemented in the next commit. This will allow automated handling of two property/fieldname pairs for this `Handler`. * refactor: add `exceptions.ReadonlyError`, `exceptions.ReadonlyCheckError` * refactor: add `gcal.GoogleCalendarInterface.get_events()` change previous uses of `get_cal_service().events()` to `get_events()` * refactor: add `gcal.GoogleCalendarInterface.delete()` * refactor: add gcalcli.actions._iter_field_handlers() * style: add extra line of whitespace * ci: eliminate flake8-docstrings method docstring checks for details.py These are mostly overriding pure virtual classes and adding a docstring to each one would be superfluous and make the code harder to read.
The main parts of this are done. I haven't closed the issue because each of the "Future extensions or subsequent refactoring" should either be (1) implemented, (2) made into issues for future discussion or implementation, or (3) decided as WONTFIX. |
K, I've forked off some new issues for the loose ends:
I'll close out this base issue and we can follow up on subtasks in those or fresh bugs. |
Thank you @dbarnett for cleaning this up. |
I often need to do bulk editing of my calendar which is not super convenient with the Google Calendar web interface. A spreadsheet interface would be much better.
What would you think about a way to do roundtrip updates through
gcalcli agenda --tsv
and a paired commandagendaupdate
for writing any changes back? We would need:gcalcli.gcal.GoogleCalendarInterface._tsv()
. I would like to replace the current long list of if statements with a modular design based around new classgcalcli.details.Handler
. EachHandler
represents the set of event properties that are captured by each item ofgcalcli.argparsers.DETAILS
. These objects would haveget()
handlers for TSV. They would be stored in acollections.OrderedDict
calledgcalcli.details.HANDLERS
.gcalcli.argparsers.DETAILS
to a superset ofgcalcli.details.HANDLERS.keys()
to avoid duplication.--header
.Handler
forid
to make it trivial to find the right Event later for the round trip.Handler.patch()
for a limited set of details. Throw error if there is a column not in the implemented set.Handler.patch()
for the rest of the details.Future extensions or subsequent refactoring could include:
details.Handler
subclassesDetail.format()
handlers that could be used forgcalcli.gcal.GoogleCalendarInterface._PrintEvent()
.The text was updated successfully, but these errors were encountered: