From e214746063271e6f9f586286103ed051ada49d85 Mon Sep 17 00:00:00 2001 From: mwort Date: Mon, 20 Jan 2020 00:55:43 +0100 Subject: [PATCH 1/3] Refactor cli: singular --db, --json and query options. --- osxphotos/__main__.py | 408 ++++++++++++++---------------------------- 1 file changed, 131 insertions(+), 277 deletions(-) diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 7639dbc0..051cb0a1 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -64,24 +64,126 @@ class CLI_Obj: CTX_SETTINGS = dict(help_option_names=["-h", "--help"]) - - -@click.group(context_settings=CTX_SETTINGS) -@click.option( +DB_OPTION = click.option( "--db", required=False, metavar="", default=None, - help="Specify Photos database path.", + help=( + "Specify Photos database path. " + "Path to Photos library/database can be specified using either --db " + "or directly as PHOTOS_LIBRARY positional argument." + ), type=click.Path(exists=True), ) -@click.option( + +DB_ARGUMENT = click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) + +JSON_OPTION = click.option( "--json", required=False, is_flag=True, default=False, help="Print output in JSON format.", ) + + +def query_options(f): + o = click.option + options = [ + o("--keyword", default=None, multiple=True, help="Search for keyword(s)."), + o("--person", default=None, multiple=True, help="Search for person(s)."), + o("--album", default=None, multiple=True, help="Search for album(s)."), + o("--uuid", default=None, multiple=True, help="Search for UUID(s)."), + o( + "--title", + default=None, + multiple=True, + help="Search for TEXT in title of photo.", + ), + o("--no-title", is_flag=True, help="Search for photos with no title."), + o( + "--description", + default=None, + multiple=True, + help="Search for TEXT in description of photo.", + ), + o( + "--no-description", + is_flag=True, + help="Search for photos with no description.", + ), + o( + "--uti", + default=None, + multiple=False, + help="Search for photos whose uniform type identifier (UTI) matches TEXT", + ), + o( + "-i", + "--ignore-case", + is_flag=True, + help="Case insensitive search for title or description. Does not apply to keyword, person, or album.", + ), + o("--edited", is_flag=True, help="Search for photos that have been edited."), + o( + "--external-edit", + is_flag=True, + help="Search for photos edited in external editor.", + ), + o("--favorite", is_flag=True, help="Search for photos marked favorite."), + o( + "--not-favorite", + is_flag=True, + help="Search for photos not marked favorite.", + ), + o("--hidden", is_flag=True, help="Search for photos marked hidden."), + o("--not-hidden", is_flag=True, help="Search for photos not marked hidden."), + o( + "--shared", + is_flag=True, + help="Search for photos in shared iCloud album (Photos 5 only).", + ), + o( + "--not-shared", + is_flag=True, + help="Search for photos not in shared iCloud album (Photos 5 only).", + ), + o( + "--burst", + is_flag=True, + help="Search for photos that were taken in a burst.", + ), + o( + "--not-burst", + is_flag=True, + help="Search for photos that are not part of a burst.", + ), + o("--live", is_flag=True, help="Search for Apple live photos"), + o( + "--not-live", + is_flag=True, + help="Search for photos that are not Apple live photos", + ), + o( + "--only-movies", + is_flag=True, + help="Search only for movies (default searches both images and movies).", + ), + o( + "--only-photos", + is_flag=True, + help="Search only for photos/images (default searches both images and movies).", + ), + ] + for o in options[::-1]: + f = o(f) + return f + + +@click.group(context_settings=CTX_SETTINGS) +@DB_OPTION +@JSON_OPTION @click.option("--debug", required=False, is_flag=True, default=False, hidden=True) @click.version_option(__version__, "--version", "-v") @click.pass_context @@ -90,25 +192,9 @@ def cli(ctx, db, json, debug): @cli.command() -@click.option( - "--db", - required=False, - metavar="", - default=None, - help="Specify Photos database path. " - "Path to Photos library/database can be specified using either --db " - "or directly as PHOTOS_LIBRARY positional argument.", - type=click.Path(exists=True), -) -@click.option( - "--json", - "json_", - required=False, - is_flag=True, - default=False, - help="Print output in JSON format.", -) -@click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) +@DB_OPTION +@JSON_OPTION +@DB_ARGUMENT @click.pass_obj @click.pass_context def keywords(ctx, cli_obj, db, json_, photos_library): @@ -130,25 +216,9 @@ def keywords(ctx, cli_obj, db, json_, photos_library): @cli.command() -@click.option( - "--db", - required=False, - metavar="", - default=None, - help="Specify Photos database path. " - "Path to Photos library/database can be specified using either --db " - "or directly as PHOTOS_LIBRARY positional argument.", - type=click.Path(exists=True), -) -@click.option( - "--json", - "json_", - required=False, - is_flag=True, - default=False, - help="Print output in JSON format.", -) -@click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) +@DB_OPTION +@JSON_OPTION +@DB_ARGUMENT @click.pass_obj @click.pass_context def albums(ctx, cli_obj, db, json_, photos_library): @@ -173,25 +243,9 @@ def albums(ctx, cli_obj, db, json_, photos_library): @cli.command() -@click.option( - "--db", - required=False, - metavar="", - default=None, - help="Specify Photos database path. " - "Path to Photos library/database can be specified using either --db " - "or directly as PHOTOS_LIBRARY positional argument.", - type=click.Path(exists=True), -) -@click.option( - "--json", - "json_", - required=False, - is_flag=True, - default=False, - help="Print output in JSON format.", -) -@click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) +@DB_OPTION +@JSON_OPTION +@DB_ARGUMENT @click.pass_obj @click.pass_context def persons(ctx, cli_obj, db, json_, photos_library): @@ -213,25 +267,9 @@ def persons(ctx, cli_obj, db, json_, photos_library): @cli.command() -@click.option( - "--db", - required=False, - metavar="", - default=None, - help="Specify Photos database path. " - "Path to Photos library/database can be specified using either --db " - "or directly as PHOTOS_LIBRARY positional argument.", - type=click.Path(exists=True), -) -@click.option( - "--json", - "json_", - required=False, - is_flag=True, - default=False, - help="Print output in JSON format.", -) -@click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) +@DB_OPTION +@JSON_OPTION +@DB_ARGUMENT @click.pass_obj @click.pass_context def info(ctx, cli_obj, db, json_, photos_library): @@ -302,25 +340,9 @@ def info(ctx, cli_obj, db, json_, photos_library): @cli.command() -@click.option( - "--db", - required=False, - metavar="", - default=None, - help="Specify Photos database path. " - "Path to Photos library/database can be specified using either --db " - "or directly as PHOTOS_LIBRARY positional argument.", - type=click.Path(exists=True), -) -@click.option( - "--json", - "json_", - required=False, - is_flag=True, - default=False, - help="Print output in JSON format.", -) -@click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) +@DB_OPTION +@JSON_OPTION +@DB_ARGUMENT @click.pass_obj @click.pass_context def dump(ctx, cli_obj, db, json_, photos_library): @@ -339,14 +361,7 @@ def dump(ctx, cli_obj, db, json_, photos_library): @cli.command(name="list") -@click.option( - "--json", - "json_", - required=False, - is_flag=True, - default=False, - help="Print output in JSON format.", -) +@JSON_OPTION @click.pass_obj @click.pass_context def list_libraries(ctx, cli_obj, json_): @@ -391,89 +406,15 @@ def _list_libraries(json_=False): @cli.command() -@click.option( - "--db", - required=False, - metavar="", - default=None, - help="Specify Photos database path. " - "Path to Photos library/database can be specified using either --db " - "or directly as PHOTOS_LIBRARY positional argument.", - type=click.Path(exists=True), -) -@click.option( - "--json", - "json_", - required=False, - is_flag=True, - default=False, - help="Print output in JSON format.", -) -@click.option("--keyword", default=None, multiple=True, help="Search for keyword(s).") -@click.option("--person", default=None, multiple=True, help="Search for person(s).") -@click.option("--album", default=None, multiple=True, help="Search for album(s).") -@click.option("--uuid", default=None, multiple=True, help="Search for UUID(s).") -@click.option( - "--title", default=None, multiple=True, help="Search for TEXT in title of photo." -) -@click.option("--no-title", is_flag=True, help="Search for photos with no title.") -@click.option( - "--description", - default=None, - multiple=True, - help="Search for TEXT in description of photo.", -) -@click.option( - "--no-description", is_flag=True, help="Search for photos with no description." -) -@click.option( - "--uti", - default=None, - multiple=False, - help="Search for photos whose uniform type identifier (UTI) matches TEXT", -) -@click.option( - "-i", - "--ignore-case", - is_flag=True, - help="Case insensitive search for title or description. Does not apply to keyword, person, or album.", -) -@click.option("--edited", is_flag=True, help="Search for photos that have been edited.") -@click.option( - "--external-edit", is_flag=True, help="Search for photos edited in external editor." -) -@click.option("--favorite", is_flag=True, help="Search for photos marked favorite.") -@click.option( - "--not-favorite", is_flag=True, help="Search for photos not marked favorite." -) -@click.option("--hidden", is_flag=True, help="Search for photos marked hidden.") -@click.option("--not-hidden", is_flag=True, help="Search for photos not marked hidden.") +@DB_OPTION +@JSON_OPTION +@query_options @click.option("--missing", is_flag=True, help="Search for photos missing from disk.") @click.option( "--not-missing", is_flag=True, help="Search for photos present on disk (e.g. not missing).", ) -@click.option( - "--shared", - is_flag=True, - help="Search for photos in shared iCloud album (Photos 5 only).", -) -@click.option( - "--not-shared", - is_flag=True, - help="Search for photos not in shared iCloud album (Photos 5 only).", -) -@click.option( - "--burst", is_flag=True, help="Search for photos that were taken in a burst." -) -@click.option( - "--not-burst", is_flag=True, help="Search for photos that are not part of a burst." -) -@click.option("--live", is_flag=True, help="Search for Apple live photos") -@click.option( - "--not-live", is_flag=True, help="Search for photos that are not Apple live photos" -) @click.option( "--cloudasset", is_flag=True, @@ -494,17 +435,7 @@ def _list_libraries(json_=False): is_flag=True, help="Search for photos that are not in iCloud (have not been synched)", ) -@click.option( - "--only-movies", - is_flag=True, - help="Search only for movies (default searches both images and movies).", -) -@click.option( - "--only-photos", - is_flag=True, - help="Search only for photos/images (default searches both images and movies).", -) -@click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) +@DB_ARGUMENT @click.pass_obj @click.pass_context def query( @@ -677,75 +608,8 @@ def query( @cli.command() -@click.option( - "--db", - required=False, - metavar="", - default=None, - help="Specify Photos database path. " - "Path to Photos library/database can be specified using either --db " - "or directly as PHOTOS_LIBRARY positional argument.", - type=click.Path(exists=True), -) -@click.option("--keyword", default=None, multiple=True, help="Search for keyword(s).") -@click.option("--person", default=None, multiple=True, help="Search for person(s).") -@click.option("--album", default=None, multiple=True, help="Search for album(s).") -@click.option("--uuid", default=None, multiple=True, help="Search for UUID(s).") -@click.option( - "--title", default=None, multiple=True, help="Search for TEXT in title of photo." -) -@click.option("--no-title", is_flag=True, help="Search for photos with no title.") -@click.option( - "--description", - default=None, - multiple=True, - help="Search for TEXT in description of photo.", -) -@click.option( - "--no-description", is_flag=True, help="Search for photos with no description." -) -@click.option( - "--uti", - default=None, - multiple=False, - help="Search for photos whose uniform type identifier (UTI) matches TEXT", -) -@click.option( - "-i", - "--ignore-case", - is_flag=True, - help="Case insensitive search for title or description. Does not apply to keyword, person, or album.", -) -@click.option("--edited", is_flag=True, help="Search for photos that have been edited.") -@click.option( - "--external-edit", is_flag=True, help="Search for photos edited in external editor." -) -@click.option("--favorite", is_flag=True, help="Search for photos marked favorite.") -@click.option( - "--not-favorite", is_flag=True, help="Search for photos not marked favorite." -) -@click.option("--hidden", is_flag=True, help="Search for photos marked hidden.") -@click.option("--not-hidden", is_flag=True, help="Search for photos not marked hidden.") -@click.option( - "--burst", is_flag=True, help="Search for photos that were taken in a burst." -) -@click.option( - "--not-burst", is_flag=True, help="Search for photos that are not part of a burst." -) -@click.option("--live", is_flag=True, help="Search for Apple live photos") -@click.option( - "--not-live", is_flag=True, help="Search for photos that are not Apple live photos" -) -@click.option( - "--shared", - is_flag=True, - help="Search for photos in shared iCloud album (Photos 5 only).", -) -@click.option( - "--not-shared", - is_flag=True, - help="Search for photos not in shared iCloud album (Photos 5 only).", -) +@DB_OPTION +@query_options @click.option("--verbose", "-V", is_flag=True, help="Print verbose output.") @click.option( "--overwrite", @@ -793,16 +657,6 @@ def query( "The sidecar file is named in format photoname.ext.json where ext is extension of the photo (e.g. jpg). " "Note: this does not create an XMP sidecar as used by Lightroom, etc.", ) -@click.option( - "--only-movies", - is_flag=True, - help="Search only for movies (default searches both images and movies).", -) -@click.option( - "--only-photos", - is_flag=True, - help="Search only for photos/images (default searches both images and movies).", -) @click.option( "--download-missing", is_flag=True, @@ -811,7 +665,7 @@ def query( "the photo does not exist on disk. This will be slow and will require internet connection. " "This obviously only works if the Photos library is synched to iCloud.", ) -@click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) +@DB_ARGUMENT @click.argument("dest", nargs=1, type=click.Path(exists=True)) @click.pass_obj @click.pass_context From 6e364fc9d987fefe3a751e0ab40d63bc7dbe9409 Mon Sep 17 00:00:00 2001 From: mwort Date: Mon, 20 Jan 2020 00:56:23 +0100 Subject: [PATCH 2/3] Write out list of libraries to stderr except in list command. --- osxphotos/__main__.py | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 051cb0a1..00e50963 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -202,8 +202,8 @@ def keywords(ctx, cli_obj, db, json_, photos_library): db = get_photos_db(*photos_library, db, cli_obj.db) if db is None: - click.echo(cli.commands["keywords"].get_help(ctx)) - click.echo("\n\nLocated the following Photos library databases: ") + click.echo(cli.commands["keywords"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) _list_libraries() return @@ -226,8 +226,8 @@ def albums(ctx, cli_obj, db, json_, photos_library): db = get_photos_db(*photos_library, db, cli_obj.db) if db is None: - click.echo(cli.commands["albums"].get_help(ctx)) - click.echo("\n\nLocated the following Photos library databases: ") + click.echo(cli.commands["albums"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) _list_libraries() return @@ -253,8 +253,8 @@ def persons(ctx, cli_obj, db, json_, photos_library): db = get_photos_db(*photos_library, db, cli_obj.db) if db is None: - click.echo(cli.commands["persons"].get_help(ctx)) - click.echo("\n\nLocated the following Photos library databases: ") + click.echo(cli.commands["persons"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) _list_libraries() return @@ -277,8 +277,8 @@ def info(ctx, cli_obj, db, json_, photos_library): db = get_photos_db(*photos_library, db, cli_obj.db) if db is None: - click.echo(cli.commands["info"].get_help(ctx)) - click.echo("\n\nLocated the following Photos library databases: ") + click.echo(cli.commands["info"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) _list_libraries() return @@ -350,8 +350,8 @@ def dump(ctx, cli_obj, db, json_, photos_library): db = get_photos_db(*photos_library, db, cli_obj.db) if db is None: - click.echo(cli.commands["dump"].get_help(ctx)) - click.echo("\n\nLocated the following Photos library databases: ") + click.echo(cli.commands["dump"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) _list_libraries() return @@ -366,10 +366,10 @@ def dump(ctx, cli_obj, db, json_, photos_library): @click.pass_context def list_libraries(ctx, cli_obj, json_): """ Print list of Photos libraries found on the system. """ - _list_libraries(json_=json_ or cli_obj.json) + _list_libraries(json_=json_ or cli_obj.json, error=False) -def _list_libraries(json_=False): +def _list_libraries(json_=False, error=True): """ Print list of Photos libraries found on the system. If json_ == True, print output as JSON (default = False) """ @@ -389,20 +389,20 @@ def _list_libraries(json_=False): for lib in photo_libs: if lib == sys_lib: - click.echo(f"(*)\t{lib}") + click.echo(f"(*)\t{lib}", err=error) sys_lib_flag = True elif lib == last_lib: - click.echo(f"(#)\t{lib}") + click.echo(f"(#)\t{lib}", err=error) last_lib_flag = True else: - click.echo(f"\t{lib}") + click.echo(f"\t{lib}", err=error) if sys_lib_flag or last_lib_flag: - click.echo("\n") + click.echo("\n", err=error) if sys_lib_flag: - click.echo("(*)\tSystem Photos Library") + click.echo("(*)\tSystem Photos Library", err=error) if last_lib_flag: - click.echo("(#)\tLast opened Photos Library") + click.echo("(#)\tLast opened Photos Library", err=error) @cli.command() @@ -566,8 +566,8 @@ def query( db = get_photos_db(*photos_library, db, cli_obj.db) if db is None: - click.echo(cli.commands["query"].get_help(ctx)) - click.echo("\n\nLocated the following Photos library databases: ") + click.echo(cli.commands["query"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) _list_libraries() return @@ -758,8 +758,8 @@ def export( db = get_photos_db(*photos_library, db, cli_obj.db) if db is None: - click.echo(cli.commands["export"].get_help(ctx)) - click.echo("\n\nLocated the following Photos library databases: ") + click.echo(cli.commands["export"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) _list_libraries() return @@ -854,7 +854,7 @@ def help(ctx, topic, **kw): if topic is None: click.echo(ctx.parent.get_help()) else: - ctx.info_name = topic + ctx.info_name = topic click.echo(cli.commands[topic].get_help(ctx)) From a2067709ccb1648454e732421fbf436a73e1d842 Mon Sep 17 00:00:00 2001 From: mwort Date: Mon, 20 Jan 2020 01:09:55 +0100 Subject: [PATCH 3/3] Fix json_ argument to cli. --- osxphotos/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 00e50963..8e4c9879 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -80,7 +80,7 @@ DB_OPTION = click.option( DB_ARGUMENT = click.argument("photos_library", nargs=-1, type=click.Path(exists=True)) JSON_OPTION = click.option( - "--json", + "--json", "json_", required=False, is_flag=True, default=False, @@ -187,8 +187,8 @@ def query_options(f): @click.option("--debug", required=False, is_flag=True, default=False, hidden=True) @click.version_option(__version__, "--version", "-v") @click.pass_context -def cli(ctx, db, json, debug): - ctx.obj = CLI_Obj(db=db, json=json, debug=debug) +def cli(ctx, db, json_, debug): + ctx.obj = CLI_Obj(db=db, json=json_, debug=debug) @cli.command()