Merge pull request #55 from mwort/refactor-cli

Refactor CLI
This commit is contained in:
Rhet Turnbull
2020-01-19 18:07:15 -08:00
committed by GitHub

View File

@@ -64,51 +64,137 @@ class CLI_Obj:
CTX_SETTINGS = dict(help_option_names=["-h", "--help"]) CTX_SETTINGS = dict(help_option_names=["-h", "--help"])
DB_OPTION = click.option(
"--db",
required=False,
metavar="<Photos database path>",
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),
)
DB_ARGUMENT = click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
JSON_OPTION = click.option(
"--json", "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) @click.group(context_settings=CTX_SETTINGS)
@click.option( @DB_OPTION
"--db", @JSON_OPTION
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.option("--debug", required=False, is_flag=True, default=False, hidden=True) @click.option("--debug", required=False, is_flag=True, default=False, hidden=True)
@click.version_option(__version__, "--version", "-v") @click.version_option(__version__, "--version", "-v")
@click.pass_context @click.pass_context
def cli(ctx, db, json, debug): def cli(ctx, db, json_, debug):
ctx.obj = CLI_Obj(db=db, json=json, debug=debug) ctx.obj = CLI_Obj(db=db, json=json_, debug=debug)
@cli.command() @cli.command()
@click.option( @DB_OPTION
"--db", @JSON_OPTION
required=False, @DB_ARGUMENT
metavar="<Photos database path>",
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))
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
def keywords(ctx, cli_obj, db, json_, photos_library): def keywords(ctx, cli_obj, db, json_, photos_library):
@@ -116,8 +202,8 @@ def keywords(ctx, cli_obj, db, json_, photos_library):
db = get_photos_db(*photos_library, db, cli_obj.db) db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None: if db is None:
click.echo(cli.commands["keywords"].get_help(ctx)) click.echo(cli.commands["keywords"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ") click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries() _list_libraries()
return return
@@ -130,25 +216,9 @@ def keywords(ctx, cli_obj, db, json_, photos_library):
@cli.command() @cli.command()
@click.option( @DB_OPTION
"--db", @JSON_OPTION
required=False, @DB_ARGUMENT
metavar="<Photos database path>",
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))
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
def albums(ctx, cli_obj, db, json_, photos_library): def albums(ctx, cli_obj, db, json_, photos_library):
@@ -156,8 +226,8 @@ def albums(ctx, cli_obj, db, json_, photos_library):
db = get_photos_db(*photos_library, db, cli_obj.db) db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None: if db is None:
click.echo(cli.commands["albums"].get_help(ctx)) click.echo(cli.commands["albums"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ") click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries() _list_libraries()
return return
@@ -173,25 +243,9 @@ def albums(ctx, cli_obj, db, json_, photos_library):
@cli.command() @cli.command()
@click.option( @DB_OPTION
"--db", @JSON_OPTION
required=False, @DB_ARGUMENT
metavar="<Photos database path>",
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))
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
def persons(ctx, cli_obj, db, json_, photos_library): def persons(ctx, cli_obj, db, json_, photos_library):
@@ -199,8 +253,8 @@ def persons(ctx, cli_obj, db, json_, photos_library):
db = get_photos_db(*photos_library, db, cli_obj.db) db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None: if db is None:
click.echo(cli.commands["persons"].get_help(ctx)) click.echo(cli.commands["persons"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ") click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries() _list_libraries()
return return
@@ -213,25 +267,9 @@ def persons(ctx, cli_obj, db, json_, photos_library):
@cli.command() @cli.command()
@click.option( @DB_OPTION
"--db", @JSON_OPTION
required=False, @DB_ARGUMENT
metavar="<Photos database path>",
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))
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
def info(ctx, cli_obj, db, json_, photos_library): def info(ctx, cli_obj, db, json_, photos_library):
@@ -239,8 +277,8 @@ def info(ctx, cli_obj, db, json_, photos_library):
db = get_photos_db(*photos_library, db, cli_obj.db) db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None: if db is None:
click.echo(cli.commands["info"].get_help(ctx)) click.echo(cli.commands["info"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ") click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries() _list_libraries()
return return
@@ -302,25 +340,9 @@ def info(ctx, cli_obj, db, json_, photos_library):
@cli.command() @cli.command()
@click.option( @DB_OPTION
"--db", @JSON_OPTION
required=False, @DB_ARGUMENT
metavar="<Photos database path>",
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))
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
def dump(ctx, cli_obj, db, json_, photos_library): def dump(ctx, cli_obj, db, json_, photos_library):
@@ -328,8 +350,8 @@ def dump(ctx, cli_obj, db, json_, photos_library):
db = get_photos_db(*photos_library, db, cli_obj.db) db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None: if db is None:
click.echo(cli.commands["dump"].get_help(ctx)) click.echo(cli.commands["dump"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ") click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries() _list_libraries()
return return
@@ -339,22 +361,15 @@ def dump(ctx, cli_obj, db, json_, photos_library):
@cli.command(name="list") @cli.command(name="list")
@click.option( @JSON_OPTION
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
def list_libraries(ctx, cli_obj, json_): def list_libraries(ctx, cli_obj, json_):
""" Print list of Photos libraries found on the system. """ """ 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. """ Print list of Photos libraries found on the system.
If json_ == True, print output as JSON (default = False) """ If json_ == True, print output as JSON (default = False) """
@@ -374,106 +389,32 @@ def _list_libraries(json_=False):
for lib in photo_libs: for lib in photo_libs:
if lib == sys_lib: if lib == sys_lib:
click.echo(f"(*)\t{lib}") click.echo(f"(*)\t{lib}", err=error)
sys_lib_flag = True sys_lib_flag = True
elif lib == last_lib: elif lib == last_lib:
click.echo(f"(#)\t{lib}") click.echo(f"(#)\t{lib}", err=error)
last_lib_flag = True last_lib_flag = True
else: else:
click.echo(f"\t{lib}") click.echo(f"\t{lib}", err=error)
if sys_lib_flag or last_lib_flag: if sys_lib_flag or last_lib_flag:
click.echo("\n") click.echo("\n", err=error)
if sys_lib_flag: if sys_lib_flag:
click.echo("(*)\tSystem Photos Library") click.echo("(*)\tSystem Photos Library", err=error)
if last_lib_flag: if last_lib_flag:
click.echo("(#)\tLast opened Photos Library") click.echo("(#)\tLast opened Photos Library", err=error)
@cli.command() @cli.command()
@click.option( @DB_OPTION
"--db", @JSON_OPTION
required=False, @query_options
metavar="<Photos database path>",
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.")
@click.option("--missing", is_flag=True, help="Search for photos missing from disk.") @click.option("--missing", is_flag=True, help="Search for photos missing from disk.")
@click.option( @click.option(
"--not-missing", "--not-missing",
is_flag=True, is_flag=True,
help="Search for photos present on disk (e.g. not missing).", 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( @click.option(
"--cloudasset", "--cloudasset",
is_flag=True, is_flag=True,
@@ -494,17 +435,7 @@ def _list_libraries(json_=False):
is_flag=True, is_flag=True,
help="Search for photos that are not in iCloud (have not been synched)", help="Search for photos that are not in iCloud (have not been synched)",
) )
@click.option( @DB_ARGUMENT
"--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))
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
def query( def query(
@@ -635,8 +566,8 @@ def query(
db = get_photos_db(*photos_library, db, cli_obj.db) db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None: if db is None:
click.echo(cli.commands["query"].get_help(ctx)) click.echo(cli.commands["query"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ") click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries() _list_libraries()
return return
@@ -677,75 +608,8 @@ def query(
@cli.command() @cli.command()
@click.option( @DB_OPTION
"--db", @query_options
required=False,
metavar="<Photos database path>",
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).",
)
@click.option("--verbose", "-V", is_flag=True, help="Print verbose output.") @click.option("--verbose", "-V", is_flag=True, help="Print verbose output.")
@click.option( @click.option(
"--overwrite", "--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). " "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.", "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( @click.option(
"--download-missing", "--download-missing",
is_flag=True, 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. " "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.", "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.argument("dest", nargs=1, type=click.Path(exists=True))
@click.pass_obj @click.pass_obj
@click.pass_context @click.pass_context
@@ -904,8 +758,8 @@ def export(
db = get_photos_db(*photos_library, db, cli_obj.db) db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None: if db is None:
click.echo(cli.commands["export"].get_help(ctx)) click.echo(cli.commands["export"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ") click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries() _list_libraries()
return return
@@ -1000,7 +854,7 @@ def help(ctx, topic, **kw):
if topic is None: if topic is None:
click.echo(ctx.parent.get_help()) click.echo(ctx.parent.get_help())
else: else:
ctx.info_name = topic ctx.info_name = topic
click.echo(cli.commands[topic].get_help(ctx)) click.echo(cli.commands[topic].get_help(ctx))