Digitale bierlijst

cli.py 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import click
  2. from piket_client.model import (
  3. AardbeiActivity,
  4. ServerStatus,
  5. NetworkError,
  6. Consumption,
  7. AardbeiPeopleDiff,
  8. Person,
  9. Settlement,
  10. ConsumptionType,
  11. )
  12. from prettytable import PrettyTable
  13. @click.group()
  14. def cli():
  15. """Poke coco from the command line."""
  16. pass
  17. @cli.command()
  18. def status():
  19. """Show the current status of the server."""
  20. status = ServerStatus.is_server_running()
  21. if isinstance(status, NetworkError):
  22. print_error(f"Failed to get data from server, error {status.value}")
  23. return
  24. print_ok("Server is available.")
  25. open_consumptions = ServerStatus.unsettled_consumptions()
  26. if isinstance(open_consumptions, NetworkError):
  27. print_error(
  28. f"Failed to get unsettled consumptions, error {open_consumptions.value}"
  29. )
  30. return
  31. click.echo(f"There are {open_consumptions.amount} unsettled consumptions.")
  32. if open_consumptions.amount > 0:
  33. click.echo(f"First at: {open_consumptions.first_timestamp.strftime('%c')}")
  34. click.echo(f"Most recent at: {open_consumptions.last_timestamp.strftime('%c')}")
  35. @cli.group()
  36. def people():
  37. pass
  38. @people.command("list")
  39. @click.option("--active/--inactive", default=None)
  40. def list_people(active: bool) -> None:
  41. people = Person.get_all(active=active)
  42. if isinstance(people, NetworkError):
  43. print_error(f"Could not get people: {people.value}")
  44. return
  45. table = PrettyTable()
  46. table.field_names = ["ID", "Full name", "Display name", "Active"]
  47. table.align["ID"] = "r"
  48. table.align["Full name"] = "l"
  49. table.align["Display name"] = "l"
  50. table.sortby = "Full name"
  51. for p in people:
  52. table.add_row([p.person_id, p.full_name, p.display_name, p.active])
  53. print(table)
  54. @people.command("create")
  55. @click.option("--display-name", type=click.STRING)
  56. @click.argument("name", type=click.STRING)
  57. def create_person(name: str, display_name: str) -> None:
  58. """Create a person."""
  59. person = Person(full_name=name, display_name=display_name).create()
  60. if isinstance(person, NetworkError):
  61. print_error(f"Could not create Person: {person.value}")
  62. return
  63. print_ok(f'Created person "{name}" with ID {person.person_id}.')
  64. @cli.group()
  65. def settlements():
  66. pass
  67. @settlements.command("show")
  68. @click.argument("settlement_id", type=click.INT)
  69. def show_settlement(settlement_id: int) -> None:
  70. """Get and view the contents of a Settlement."""
  71. s = Settlement.get(settlement_id)
  72. if isinstance(s, NetworkError):
  73. print_error(f"Could not get Settlement: {s.value}")
  74. return
  75. output_settlement_info(s)
  76. @settlements.command("create")
  77. @click.argument("name")
  78. def create_settlement(name: str) -> None:
  79. """Create a new Settlement."""
  80. s = Settlement.create(name)
  81. if isinstance(s, NetworkError):
  82. print_error(f"Could not create Settlement: {s.value}")
  83. return
  84. output_settlement_info(s)
  85. def output_settlement_info(s: Settlement) -> None:
  86. click.echo(f'Settlement {s.settlement_id}, "{s.name}"')
  87. click.echo(f"Summary:")
  88. for key, value in s.consumption_summary.items():
  89. click.echo(f" - {value['count']} {value['name']} ({key})")
  90. ct_name_by_id = {key: value["name"] for key, value in s.consumption_summary.items()}
  91. table = PrettyTable()
  92. table.field_names = ["Name", *ct_name_by_id.values()]
  93. table.sortby = "Name"
  94. table.align = "r"
  95. table.align["Name"] = "l" # type: ignore
  96. zero_fields = {k: "" for k in ct_name_by_id.values()}
  97. for item in s.per_person_counts.values():
  98. r = {"Name": item["full_name"], **zero_fields}
  99. for key, value in item["counts"].items():
  100. r[ct_name_by_id[key]] = value
  101. table.add_row(r.values())
  102. print(table)
  103. @cli.group()
  104. def consumption_types():
  105. pass
  106. @consumption_types.command("list")
  107. def list_consumption_types() -> None:
  108. active = ConsumptionType.get_all(active=True)
  109. inactive = ConsumptionType.get_all(active=False)
  110. if isinstance(active, NetworkError) or isinstance(inactive, NetworkError):
  111. print_error("Could not get consumption types!")
  112. return
  113. table = PrettyTable()
  114. table.field_names = ["ID", "Name", "Active"]
  115. table.sortby = "ID"
  116. for ct in active + inactive:
  117. table.add_row([ct.consumption_type_id, ct.name, ct.active])
  118. print(table)
  119. @consumption_types.command("create")
  120. @click.argument("name")
  121. def create_consumption_type(name: str) -> None:
  122. ct = ConsumptionType(name=name).create()
  123. if not isinstance(ct, NetworkError):
  124. print_ok(f'Created consumption type "{name}" with ID {ct.consumption_type_id}.')
  125. @consumption_types.command("activate")
  126. @click.argument("consumption_type_id", type=click.INT)
  127. def activate_consumption_type(consumption_type_id: int) -> None:
  128. ct = ConsumptionType.get(consumption_type_id)
  129. if isinstance(ct, NetworkError):
  130. print_error(f"Could not get ConsumptionType: {ct.value}")
  131. return
  132. result = ct.set_active(True)
  133. if not isinstance(result, NetworkError):
  134. print_ok(
  135. f"Consumption type {ct.consumption_type_id} ({ct.name}) is now active."
  136. )
  137. @consumption_types.command("deactivate")
  138. @click.argument("consumption_type_id", type=click.INT)
  139. def deactivate_consumption_type(consumption_type_id: int) -> None:
  140. ct = ConsumptionType.get(consumption_type_id)
  141. if isinstance(ct, NetworkError):
  142. print_error(f"Could not get ConsumptionType: {ct.value}")
  143. return
  144. result = ct.set_active(False)
  145. if not isinstance(result, NetworkError):
  146. print_ok(
  147. f"Consumption type {ct.consumption_type_id} ({ct.name}) is now inactive."
  148. )
  149. def print_ok(msg: str) -> None:
  150. click.echo(click.style(msg, fg="green"))
  151. def print_error(msg: str) -> None:
  152. click.echo(click.style(msg, fg="red", bold=True), err=True)
  153. @cli.group()
  154. @click.option("--token", required=True)
  155. @click.option("--endpoint", default="http://localhost:3000")
  156. @click.pass_context
  157. def aardbei(ctx, token: str, endpoint: str) -> None:
  158. ctx.ensure_object(dict)
  159. ctx.obj["AardbeiToken"] = token
  160. ctx.obj["AardbeiEndpoint"] = endpoint
  161. @aardbei.group("activities")
  162. def aardbei_activities() -> None:
  163. pass
  164. @aardbei_activities.command("list")
  165. @click.pass_context
  166. def aardbei_list_activities(ctx) -> None:
  167. acts = AardbeiActivity.get_available(
  168. token=ctx.obj["AardbeiToken"], endpoint=ctx.obj["AardbeiEndpoint"]
  169. )
  170. if isinstance(acts, NetworkError):
  171. print_error(f"Could not get activities: {acts.value}")
  172. return
  173. table = PrettyTable()
  174. table.field_names = ["ID", "Name"]
  175. table.align = "l"
  176. for a in acts:
  177. table.add_row([a.aardbei_id, a.name])
  178. print(table)
  179. @aardbei_activities.command("apply")
  180. @click.argument("activity_id", type=click.INT)
  181. @click.pass_context
  182. def aardbei_apply_activity(ctx, activity_id: int) -> None:
  183. result = AardbeiActivity.apply_activity(
  184. token=ctx.obj["AardbeiToken"],
  185. endpoint=ctx.obj["AardbeiEndpoint"],
  186. activity_id=activity_id,
  187. )
  188. if isinstance(result, NetworkError):
  189. print_error("Failed to apply activity: {result.value}")
  190. return
  191. print_ok(f"Activity applied. There are now {result} active people.")
  192. @aardbei.group("people")
  193. def aardbei_people() -> None:
  194. pass
  195. @aardbei_people.command("diff")
  196. @click.pass_context
  197. def aardbei_diff_people(ctx) -> None:
  198. diff = AardbeiPeopleDiff.get_diff(
  199. token=ctx.obj["AardbeiToken"], endpoint=ctx.obj["AardbeiEndpoint"]
  200. )
  201. if isinstance(diff, NetworkError):
  202. print_error(f"Could not get differences: {diff.value}")
  203. return
  204. if diff.num_changes == 0:
  205. print_ok("There are no changes to apply.")
  206. return
  207. click.echo(f"There are {diff.num_changes} pending changes:")
  208. show_diff(diff)
  209. @aardbei_people.command("sync")
  210. @click.pass_context
  211. def aardbei_sync_people(ctx) -> None:
  212. diff = AardbeiPeopleDiff.sync(
  213. token=ctx.obj["AardbeiToken"], endpoint=ctx.obj["AardbeiEndpoint"]
  214. )
  215. if isinstance(diff, NetworkError):
  216. print_error(f"Could not apply differences: {diff.value}")
  217. return
  218. if diff.num_changes == 0:
  219. print_ok("There were no changes to apply.")
  220. return
  221. print_ok(f"Applied {diff.num_changes} pending changes:")
  222. show_diff(diff)
  223. def show_diff(diff: AardbeiPeopleDiff) -> None:
  224. for name in diff.new_people:
  225. click.echo(f" - Create local Person for {name}")
  226. for name in diff.link_existing:
  227. click.echo(f" - Link local and remote people for {name}")
  228. for name in diff.altered_name:
  229. click.echo(f" - Process name change for {name}")
  230. if __name__ == "__main__":
  231. cli()