opendataframework 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {opendataframework-0.0.2 → opendataframework-0.0.3}/PKG-INFO +1 -1
  2. {opendataframework-0.0.2 → opendataframework-0.0.3}/pyproject.toml +1 -1
  3. {opendataframework-0.0.2 → opendataframework-0.0.3}/setup.py +10 -2
  4. opendataframework-0.0.3/src/opendataframework/README.md +43 -0
  5. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/__init__.py +1 -1
  6. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/__main__.py +470 -12
  7. opendataframework-0.0.3/src/opendataframework/docs/index.md +1 -0
  8. opendataframework-0.0.3/src/opendataframework/docs/mkdocs.yml +56 -0
  9. opendataframework-0.0.3/src/opendataframework/docs/requirements.txt +1 -0
  10. opendataframework-0.0.3/src/opendataframework/docs/stylesheets/extra.css +8 -0
  11. opendataframework-0.0.3/src/opendataframework/github/workflows/ci.yaml +21 -0
  12. opendataframework-0.0.3/src/opendataframework/hooks/.pre-commit-config.yaml +29 -0
  13. opendataframework-0.0.3/src/opendataframework/hooks/requirements.txt +1 -0
  14. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/setup.sh +2 -0
  15. opendataframework-0.0.3/src/opendataframework/tests/__init__.py +1 -0
  16. opendataframework-0.0.3/src/opendataframework/tests/requirements.txt +2 -0
  17. opendataframework-0.0.3/src/opendataframework/tests/test_example.py +6 -0
  18. opendataframework-0.0.3/src/opendataframework/utility/nginx/Dockerfile +5 -0
  19. opendataframework-0.0.3/src/opendataframework/utility/nginx/docker-compose.yaml +14 -0
  20. opendataframework-0.0.3/src/opendataframework/utility/nginx/nginx.conf +6 -0
  21. opendataframework-0.0.3/src/opendataframework/utility/nginx/static/images/logo.svg +7 -0
  22. opendataframework-0.0.3/src/opendataframework/utility/nginx/static/index.html +39 -0
  23. opendataframework-0.0.2/src/opendataframework/README.md +0 -28
  24. opendataframework-0.0.2/src/opendataframework/analytics/build.sh +0 -2
  25. opendataframework-0.0.2/src/opendataframework/analytics/start.sh +0 -2
  26. opendataframework-0.0.2/src/opendataframework/analytics/stop.sh +0 -2
  27. opendataframework-0.0.2/src/opendataframework/api/build.sh +0 -2
  28. opendataframework-0.0.2/src/opendataframework/api/start.sh +0 -2
  29. opendataframework-0.0.2/src/opendataframework/api/stop.sh +0 -2
  30. opendataframework-0.0.2/src/opendataframework/build.sh +0 -1
  31. opendataframework-0.0.2/src/opendataframework/env.sh +0 -11
  32. opendataframework-0.0.2/src/opendataframework/main.sh +0 -42
  33. opendataframework-0.0.2/src/opendataframework/start.sh +0 -1
  34. opendataframework-0.0.2/src/opendataframework/stop.sh +0 -1
  35. opendataframework-0.0.2/src/opendataframework/storage/build.sh +0 -2
  36. opendataframework-0.0.2/src/opendataframework/storage/start.sh +0 -2
  37. opendataframework-0.0.2/src/opendataframework/storage/stop.sh +0 -2
  38. opendataframework-0.0.2/src/opendataframework/utility/build.sh +0 -2
  39. opendataframework-0.0.2/src/opendataframework/utility/start.sh +0 -2
  40. opendataframework-0.0.2/src/opendataframework/utility/stop.sh +0 -2
  41. {opendataframework-0.0.2 → opendataframework-0.0.3}/LICENSE +0 -0
  42. {opendataframework-0.0.2 → opendataframework-0.0.3}/README.md +0 -0
  43. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/analytics/setup.sh +0 -0
  44. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/analytics/superset/.env +0 -0
  45. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/analytics/superset/Dockerfile +0 -0
  46. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/analytics/superset/database/postgres/create.sh +0 -0
  47. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/analytics/superset/database/postgres/setup.sh +0 -0
  48. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/analytics/superset/docker-compose.yaml +0 -0
  49. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/analytics/superset/setup.sh +0 -0
  50. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/.env +0 -0
  51. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/Dockerfile +0 -0
  52. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/__init__.py +0 -0
  53. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/config.py +0 -0
  54. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/crud.py +0 -0
  55. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/database.py +0 -0
  56. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/dependencies.py +0 -0
  57. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/main.py +0 -0
  58. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/models.py +0 -0
  59. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/app/router.py +0 -0
  60. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/docker-compose.yaml +0 -0
  61. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/poetry.lock +0 -0
  62. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/api-postgres/pyproject.toml +0 -0
  63. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/ingest.py +0 -0
  64. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/requirements.txt +0 -0
  65. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/api/setup.sh +0 -0
  66. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/devcontainers/R/.devcontainer/.Rprofile +0 -0
  67. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/devcontainers/R/.devcontainer/Dockerfile +0 -0
  68. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/devcontainers/R/.devcontainer/devcontainer.json +0 -0
  69. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/devcontainers/python/.devcontainer/Dockerfile +0 -0
  70. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/devcontainers/python/.devcontainer/devcontainer.json +0 -0
  71. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/docker-compose.yaml +0 -0
  72. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/expectations.py +0 -0
  73. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/.vscode/extensions.json +0 -0
  74. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/code/build/.gitkeep +0 -0
  75. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/code/check/.gitkeep +0 -0
  76. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/code/learn/.gitkeep +0 -0
  77. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/code/share/.gitkeep +0 -0
  78. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/data/derived/.gitkeep +0 -0
  79. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/data/raw/.gitkeep +0 -0
  80. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/libraries/.gitkeep +0 -0
  81. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/logs/.gitkeep +0 -0
  82. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/models/.gitkeep +0 -0
  83. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/output/figures/.gitkeep +0 -0
  84. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/output/tables/.gitkeep +0 -0
  85. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/layouts/research/paper/.gitkeep +0 -0
  86. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/requirements.txt +0 -0
  87. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/storage/postgres/.env +0 -0
  88. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/storage/postgres/Dockerfile +0 -0
  89. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/storage/postgres/docker-compose.yaml +0 -0
  90. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/storage/postgres/setup.sh +0 -0
  91. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/storage/setup.sh +0 -0
  92. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/utility/setup.sh +0 -0
  93. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/utility/texlive/Dockerfile +0 -0
  94. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/utility/texlive/docker-compose.yaml +0 -0
  95. {opendataframework-0.0.2 → opendataframework-0.0.3}/src/opendataframework/utility/texlive/mnt/.gitkeep +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opendataframework
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Open Data Framework is an open source, full stack data framework.
5
5
  Home-page: https://github.com/opendataframework/opendataframework
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "opendataframework"
3
- version = "0.0.2"
3
+ version = "0.0.3"
4
4
  description = "Open Data Framework is an open source, full stack data framework."
5
5
  authors = [
6
6
  "mykytapavlov <mykytapavlov@users.noreply.github.com>",
@@ -7,7 +7,8 @@ package_dir = \
7
7
  packages = \
8
8
  ['opendataframework',
9
9
  'opendataframework.api',
10
- 'opendataframework.api.api-postgres.app']
10
+ 'opendataframework.api.api-postgres.app',
11
+ 'opendataframework.tests']
11
12
 
12
13
  package_data = \
13
14
  {'': ['*'],
@@ -16,6 +17,10 @@ package_data = \
16
17
  'analytics/superset/database/postgres/*',
17
18
  'devcontainers/R/.devcontainer/*',
18
19
  'devcontainers/python/.devcontainer/*',
20
+ 'docs/*',
21
+ 'docs/stylesheets/*',
22
+ 'github/workflows/*',
23
+ 'hooks/*',
19
24
  'layouts/research/.vscode/*',
20
25
  'layouts/research/code/build/*',
21
26
  'layouts/research/code/check/*',
@@ -32,6 +37,9 @@ package_data = \
32
37
  'storage/*',
33
38
  'storage/postgres/*',
34
39
  'utility/*',
40
+ 'utility/nginx/*',
41
+ 'utility/nginx/static/*',
42
+ 'utility/nginx/static/images/*',
35
43
  'utility/texlive/*',
36
44
  'utility/texlive/mnt/*'],
37
45
  'opendataframework.api': ['api-postgres/*']}
@@ -44,7 +52,7 @@ entry_points = \
44
52
 
45
53
  setup_kwargs = {
46
54
  'name': 'opendataframework',
47
- 'version': '0.0.2',
55
+ 'version': '0.0.3',
48
56
  'description': 'Open Data Framework is an open source, full stack data framework.',
49
57
  'long_description': '# Open Data Framework\nOpen source, full stack data framework.\n',
50
58
  'author': 'mykytapavlov',
@@ -0,0 +1,43 @@
1
+ # project_name
2
+
3
+ ### Build platform
4
+ Command builds platform's images.
5
+
6
+ ```sh
7
+ opendataframework build
8
+ ```
9
+
10
+ ### Start platform
11
+ Command starts platform's containers.
12
+
13
+ ```sh
14
+ opendataframework start
15
+ ```
16
+
17
+ ### Setup platform
18
+ Command setup platform's containers.
19
+
20
+ ```sh
21
+ opendataframework setup
22
+ ```
23
+
24
+ ### Install dependencies
25
+ Command creates `myproject/.venv/` and installs dependencies from `requirements.txt`.
26
+
27
+ ```sh
28
+ opendataframework install
29
+ ```
30
+
31
+ ### Docs
32
+ Command starts mkdocs server.
33
+
34
+ ```sh
35
+ opendataframework docs
36
+ ```
37
+
38
+ ### Test
39
+ Command runs tests.
40
+
41
+ ```sh
42
+ opendataframework test
43
+ ```
@@ -1,3 +1,3 @@
1
1
  """Open Data Framework package."""
2
2
 
3
- __version__ = "0.0.2"
3
+ __version__ = "0.0.3"
@@ -5,8 +5,11 @@ import json
5
5
  import os
6
6
  import re
7
7
  import shutil
8
+ import stat
9
+ import subprocess
8
10
  import traceback
9
11
  import uuid
12
+ import venv
10
13
  from datetime import datetime
11
14
  from pathlib import Path
12
15
 
@@ -106,6 +109,7 @@ class Component:
106
109
  POSTGRES: str = "postgres"
107
110
 
108
111
  # UTILITY
112
+ NGINX = "nginx"
109
113
  TEXLIVE = "texlive"
110
114
 
111
115
 
@@ -121,7 +125,7 @@ COMPONENTS = {
121
125
  Layer.DEVCONTAINERS: [Component.PYTHON, Component.R],
122
126
  Layer.API: [Component.API_POSTGRES],
123
127
  Layer.STORAGE: [Component.POSTGRES],
124
- Layer.UTILITY: [Component.TEXLIVE],
128
+ Layer.UTILITY: [Component.NGINX, Component.TEXLIVE],
125
129
  }
126
130
 
127
131
  DEPENDENCIES = {
@@ -140,6 +144,7 @@ DESCRIPTIONS = {
140
144
  # STORAGE
141
145
  Component.POSTGRES: "Advanced Relational Database",
142
146
  # UTILITY
147
+ Component.NGINX: "HTTP and reverse proxy server",
143
148
  Component.TEXLIVE: "TeX Live is intended to be a straightforward way to get up and running with the TeX document production system", # noqa
144
149
  }
145
150
 
@@ -151,6 +156,8 @@ PORTS = {
151
156
  Component.API_POSTGRES: "8000",
152
157
  # STORAGE
153
158
  Component.POSTGRES: "5432",
159
+ # UTILITY
160
+ Component.NGINX: "80",
154
161
  }
155
162
 
156
163
 
@@ -211,6 +218,14 @@ LAYOUTS = {Layout.CUSTOM, Layout.RESEARCH}
211
218
  PROFILES = {Profile.CUSTOM, Profile.RESEARCH}
212
219
 
213
220
 
221
+ BADGES = {
222
+ Layer.ANALYTICS: "badge badge-info gap-2",
223
+ Layer.API: "badge badge-accent gap-2",
224
+ Layer.STORAGE: "badge badge-warning gap-2",
225
+ Layer.UTILITY: "badge badge-secondary gap-2",
226
+ }
227
+
228
+
214
229
  class Field:
215
230
  """Field."""
216
231
 
@@ -690,6 +705,109 @@ class Project:
690
705
 
691
706
  rprint(f"{self.name}: {self.layout} layout[green] created[/green]")
692
707
 
708
+ def add_docs(self):
709
+ """Add project docs."""
710
+ from_path = os.path.join(SRC_PATH, "docs")
711
+ if not os.path.exists(from_path):
712
+ raise ValueError(f"{from_path} does not exist")
713
+
714
+ to_path = os.path.join(self.path, "docs")
715
+ if os.path.exists(to_path):
716
+ raise ValueError(f"{to_path} already exists")
717
+
718
+ shutil.copytree(
719
+ from_path,
720
+ to_path,
721
+ ignore=shutil.ignore_patterns(
722
+ *IGNORE_PATTERNS, *("mkdocs.yml", "requirements.txt")
723
+ ),
724
+ )
725
+
726
+ if os.path.exists(os.path.join(self.path, "requirements.txt")):
727
+ with open(os.path.join(from_path, "requirements.txt"), "r") as file:
728
+ src_file_data = file.read()
729
+ with open(os.path.join(self.path, "requirements.txt"), "a") as file:
730
+ file.write(src_file_data)
731
+ else:
732
+ self.copy(from_path, self.path, "requirements.txt")
733
+
734
+ self.copy(from_path, self.path, "mkdocs.yml")
735
+ self.replace(
736
+ os.path.join(self.path, "docs", "index.md"), PROJECT_NAME, self.name
737
+ )
738
+ self.replace(os.path.join(self.path, "mkdocs.yml"), PROJECT_NAME, self.name)
739
+
740
+ rprint(f"{self.name}: docs[green] created[/green]")
741
+
742
+ def add_hooks(self):
743
+ """Add project pre-commit hooks."""
744
+ from_path = os.path.join(SRC_PATH, "hooks")
745
+ if not os.path.exists(from_path):
746
+ raise ValueError(f"{from_path} does not exist")
747
+
748
+ if os.path.exists(os.path.join(self.path, ".pre-commit-config.yaml")):
749
+ raise ValueError(
750
+ f'{os.path.join(self.path, ".pre-commit-config.yaml")} already exists'
751
+ )
752
+
753
+ self.copy(from_path, self.path, ".pre-commit-config.yaml")
754
+
755
+ if os.path.exists(os.path.join(self.path, "requirements.txt")):
756
+ with open(os.path.join(from_path, "requirements.txt"), "r") as file:
757
+ src_file_data = file.read()
758
+ with open(os.path.join(self.path, "requirements.txt"), "a") as file:
759
+ file.write(src_file_data)
760
+ else:
761
+ self.copy(from_path, self.path, "requirements.txt")
762
+
763
+ rprint(f"{self.name}: .pre-commit-config.yaml[green] created[/green]")
764
+
765
+ def add_workflows(self):
766
+ """Add github workflows (github pages ci for docs)."""
767
+ from_path = os.path.join(SRC_PATH, "github")
768
+ if not os.path.exists(from_path):
769
+ raise ValueError(f"{from_path} does not exist")
770
+
771
+ to_path = os.path.join(self.path, ".github")
772
+ if os.path.exists(to_path):
773
+ raise ValueError(f"{to_path} already exists")
774
+
775
+ shutil.copytree(
776
+ from_path,
777
+ to_path,
778
+ ignore=shutil.ignore_patterns(
779
+ *IGNORE_PATTERNS,
780
+ ),
781
+ )
782
+
783
+ rprint(f"{self.name}: github workflows[green] created[/green]")
784
+
785
+ def add_tests(self):
786
+ """Add project tests."""
787
+ from_path = os.path.join(SRC_PATH, "tests")
788
+ if not os.path.exists(from_path):
789
+ raise ValueError(f"{from_path} does not exist")
790
+
791
+ to_path = os.path.join(self.path, "tests")
792
+ if os.path.exists(to_path):
793
+ raise ValueError(f"{to_path} already exists")
794
+
795
+ shutil.copytree(
796
+ from_path,
797
+ to_path,
798
+ ignore=shutil.ignore_patterns(*IGNORE_PATTERNS, *("requirements.txt",)),
799
+ )
800
+
801
+ if os.path.exists(os.path.join(self.path, "requirements.txt")):
802
+ with open(os.path.join(from_path, "requirements.txt"), "r") as file:
803
+ src_file_data = file.read()
804
+ with open(os.path.join(self.path, "requirements.txt"), "a") as file:
805
+ file.write(src_file_data)
806
+ else:
807
+ self.copy(from_path, self.path, "requirements.txt")
808
+
809
+ rprint(f"{self.name}: tests[green] created[/green]")
810
+
693
811
  def init(self):
694
812
  """Handler for `init` CLI command."""
695
813
  rprint()
@@ -804,10 +922,24 @@ class Project:
804
922
  rprint(f"{json.dumps(self.settings, indent=JSON_INDENT)}")
805
923
  rprint()
806
924
 
807
- def create(self):
925
+ def create(
926
+ self,
927
+ docs: bool = True,
928
+ hooks: bool = True,
929
+ workflows: bool = False,
930
+ tests: bool = True,
931
+ ):
808
932
  """Handler for `create` CLI command."""
809
933
  self.from_json()
810
934
  self.add_layout()
935
+ if docs:
936
+ self.add_docs()
937
+ if hooks:
938
+ self.add_hooks()
939
+ if workflows:
940
+ self.add_workflows()
941
+ if tests:
942
+ self.add_tests()
811
943
 
812
944
  layers = [
813
945
  Analytics(project=self),
@@ -890,7 +1022,7 @@ class Project:
890
1022
  if not os.path.exists(to_path):
891
1023
  raise ValueError(f"{to_path} does not exist")
892
1024
 
893
- scripts = ["build.sh", "setup.sh", "start.sh", "stop.sh", "requirements.txt"]
1025
+ scripts = ["setup.sh", "requirements.txt"]
894
1026
 
895
1027
  for file_name in scripts:
896
1028
  if file_name == "requirements.txt":
@@ -905,14 +1037,10 @@ class Project:
905
1037
  self.copy(from_path, to_path, file_name)
906
1038
 
907
1039
  self.copy(from_path, to_path, "docker-compose.yaml")
908
-
909
- self.copy(from_path, self.path, "env.sh")
910
1040
  self.copy(from_path, self.path, "expectations.py")
911
- self.copy(from_path, self.path, "main.sh")
912
1041
  self.copy(from_path, self.path, "README.md")
913
1042
 
914
1043
  self.replace(os.path.join(self.path, "README.md"), f"{PROJECT_NAME}", self.name)
915
- self.replace(os.path.join(self.path, "main.sh"), f"{PROJECT_NAME}", self.name)
916
1044
 
917
1045
  for layer in COMPONENTS:
918
1046
  if layer is Layer.DEVCONTAINERS:
@@ -944,11 +1072,20 @@ class Project:
944
1072
  if lines and layer == Layer.STORAGE and file_name == "setup.sh":
945
1073
  # put storage setup logic on top of setup.sh
946
1074
  with open(f"{to_path}/{file_name}", "r") as file:
947
- current_file_data = file.read()
948
-
1075
+ current_file_data = file.readlines()
1076
+ # keep shebang on top of the file
1077
+ header, current_file_data = (
1078
+ current_file_data[:3],
1079
+ current_file_data[3:],
1080
+ )
1081
+ header = [line for line in header if line != "\n"]
1082
+ header = "".join([*header])
1083
+ current_file_data = "".join([*current_file_data])
949
1084
  with open(f"{to_path}/{file_name}", "w") as file:
950
1085
  file.write(
951
- "\n".join([src_file_data, *lines, current_file_data])
1086
+ "\n".join(
1087
+ [header, src_file_data, *lines, current_file_data]
1088
+ )
952
1089
  )
953
1090
 
954
1091
  else:
@@ -1398,6 +1535,113 @@ class Utility:
1398
1535
  """Create Utility layer instance."""
1399
1536
  self.project = project
1400
1537
 
1538
+ def nginx(self):
1539
+ """Configure Analytics `NGINX` component."""
1540
+ from_path = os.path.join(SRC_PATH, Layer.UTILITY, Component.NGINX)
1541
+ if not os.path.exists(from_path):
1542
+ raise ValueError(f"{from_path} does not exist")
1543
+
1544
+ to_path = os.path.join(
1545
+ self.project.path, PLATFORM_FOLDER, Layer.UTILITY, Component.NGINX
1546
+ )
1547
+ if os.path.exists(to_path):
1548
+ raise ValueError(f"{to_path} already exists")
1549
+
1550
+ entities = self.project.settings.get("entities", {})
1551
+
1552
+ for plural_name, settings in entities.items():
1553
+ components = settings["layers"].get(Layer.UTILITY, {})
1554
+ if Component.NGINX not in components:
1555
+ continue
1556
+
1557
+ if os.path.exists(to_path):
1558
+ raise ValueError(f"{to_path} already exists")
1559
+
1560
+ shutil.copytree(
1561
+ from_path,
1562
+ to_path,
1563
+ ignore=shutil.ignore_patterns(*IGNORE_PATTERNS),
1564
+ )
1565
+
1566
+ break
1567
+
1568
+ if not os.path.exists(to_path):
1569
+ return
1570
+
1571
+ Project.replace(
1572
+ os.path.join(to_path, "docker-compose.yaml"),
1573
+ PROJECT_NAME,
1574
+ self.project.settings["project"],
1575
+ )
1576
+
1577
+ ports = self.project.settings.get("ports", {})
1578
+
1579
+ Project.replace(
1580
+ os.path.join(to_path, "docker-compose.yaml"),
1581
+ f"{PORTS[Component.NGINX]}:",
1582
+ f"{ports[Component.NGINX]}:",
1583
+ )
1584
+
1585
+ host = "http://localhost"
1586
+ target_path = os.path.join(to_path, "static", "index.html")
1587
+
1588
+ Project.replace(target_path, PROJECT_NAME, self.project.settings["project"])
1589
+
1590
+ with open(target_path, "r") as file:
1591
+ filedata = file.read()
1592
+
1593
+ header = filedata.split("<tbody>")[0]
1594
+ template, _ = filedata.split("<tbody>")[1].split("</tbody>")
1595
+ footer = filedata.split("</tbody>")[1]
1596
+
1597
+ lines = [header]
1598
+
1599
+ for component, port in ports.items():
1600
+ layer = None
1601
+ for current_layer, components in COMPONENTS.items():
1602
+ if component in components:
1603
+ layer = current_layer
1604
+ break
1605
+ if not layer:
1606
+ continue
1607
+
1608
+ tr = template.replace(
1609
+ 'class="badge badge-info gap-2">layer_name',
1610
+ f'class="{BADGES[layer]}">{layer}',
1611
+ )
1612
+ tr = tr.replace("component_name", f"{component}")
1613
+ tr = tr.replace("http://host:port/", f"{host}:{port}/")
1614
+ lines.append(tr)
1615
+
1616
+ entities = self.project.settings.get("entities", {})
1617
+
1618
+ for plural_name, settings in entities.items():
1619
+ layers = settings.get("layers", {})
1620
+
1621
+ if Component.NGINX not in layers.get(Layer.UTILITY, {}):
1622
+ continue
1623
+
1624
+ for layer, components in layers.items():
1625
+ for component in components:
1626
+ port = components[component].get("port")
1627
+ if not port:
1628
+ continue
1629
+
1630
+ tr = template.replace(
1631
+ 'class="badge badge-info gap-2">layer_name',
1632
+ f'class="{BADGES[layer]}">{layer}',
1633
+ )
1634
+ tr = tr.replace("component_name", f"{component} | {plural_name}")
1635
+ tr = tr.replace("http://host:port/", f"{host}:{port}/")
1636
+ lines.append(tr)
1637
+
1638
+ lines.append(footer)
1639
+ filedata = "\n".join(lines)
1640
+ with open(target_path, "w") as file:
1641
+ file.write(filedata)
1642
+
1643
+ rprint(f"{to_path}[green] created[/green]")
1644
+
1401
1645
  def texlive(self):
1402
1646
  """Configure Analytics `TEXLIVE` component."""
1403
1647
  from_path = os.path.join(SRC_PATH, Layer.UTILITY, Component.TEXLIVE)
@@ -1447,6 +1691,7 @@ class Utility:
1447
1691
 
1448
1692
  def __call__(self):
1449
1693
  """Call layer."""
1694
+ self.nginx()
1450
1695
  self.texlive()
1451
1696
 
1452
1697
 
@@ -1507,15 +1752,228 @@ def init(
1507
1752
 
1508
1753
 
1509
1754
  @app.command()
1510
- def create(project: str, path: str = ""):
1755
+ def create(
1756
+ project: str,
1757
+ path: str = "",
1758
+ docs: bool = True,
1759
+ hooks: bool = True,
1760
+ workflows: bool = False,
1761
+ tests: bool = True,
1762
+ ):
1511
1763
  """Create PROJECT structure based on settings.json, optionally with a --path."""
1512
1764
  try:
1513
1765
  project = Project(name=project, path=path)
1514
- project.create()
1766
+ project.create(docs=docs, hooks=hooks, workflows=workflows, tests=tests)
1515
1767
  except Exception:
1516
1768
  rprint(f"[bold red] {traceback.format_exc()} [/bold red]")
1517
1769
 
1518
1770
 
1771
+ @app.command()
1772
+ def install(project: str = "", path: str = ""):
1773
+ """Install dependencies into .venv from requirements.txt under cwd/path."""
1774
+ try:
1775
+ if path and not os.path.exists(path):
1776
+ raise ValueError(f"{path} does not exists")
1777
+ elif not path:
1778
+ path = os.getcwd()
1779
+ path = os.path.join(path, project)
1780
+ venv_path = os.path.join(path, ".venv")
1781
+ if os.path.exists(venv_path):
1782
+ raise ValueError(f"{venv_path} already exists")
1783
+
1784
+ requirements_path = os.path.join(path, "requirements.txt")
1785
+ if not os.path.exists(requirements_path):
1786
+ raise ValueError(f"{requirements_path} not exists")
1787
+
1788
+ venv.create(venv_path, with_pip=True)
1789
+ subprocess.run(["bin/pip", "install", "-r", requirements_path], cwd=venv_path)
1790
+ except Exception as e:
1791
+ rprint(f"[bold red] {e} [/bold red]")
1792
+
1793
+
1794
+ @app.command()
1795
+ def build(project: str = "", path: str = ""):
1796
+ """Run `docker compose --profile {layer} build`."""
1797
+ try:
1798
+ if path and not os.path.exists(path):
1799
+ raise ValueError(f"{path} does not exists")
1800
+ elif not path:
1801
+ path = os.getcwd()
1802
+ path = os.path.join(path, project, "platform")
1803
+
1804
+ compose_path = os.path.join(path, "docker-compose.yaml")
1805
+ if not os.path.exists(compose_path):
1806
+ raise ValueError(f"{compose_path} not exists")
1807
+
1808
+ for layer in COMPONENTS:
1809
+ subprocess.run(
1810
+ ["docker", "compose", "--profile", f"{layer}", "build"], cwd=path
1811
+ )
1812
+
1813
+ except Exception as e:
1814
+ rprint(f"[bold red] {e} [/bold red]")
1815
+
1816
+
1817
+ @app.command()
1818
+ def start(project: str = "", path: str = ""):
1819
+ """Run `docker compose --profile {layer} up -d`."""
1820
+ try:
1821
+ if path and not os.path.exists(path):
1822
+ raise ValueError(f"{path} does not exists")
1823
+ elif not path:
1824
+ path = os.getcwd()
1825
+ path = os.path.join(path, project, "platform")
1826
+
1827
+ compose_path = os.path.join(path, "docker-compose.yaml")
1828
+ if not os.path.exists(compose_path):
1829
+ raise ValueError(f"{compose_path} not exists")
1830
+
1831
+ for layer in COMPONENTS:
1832
+ subprocess.run(
1833
+ ["docker", "compose", "--profile", f"{layer}", "up", "-d"], cwd=path
1834
+ )
1835
+
1836
+ except Exception as e:
1837
+ rprint(f"[bold red] {e} [/bold red]")
1838
+
1839
+
1840
+ @app.command()
1841
+ def stop(project: str = "", path: str = ""):
1842
+ """Run `docker compose --profile {layer} stop`."""
1843
+ try:
1844
+ if path and not os.path.exists(path):
1845
+ raise ValueError(f"{path} does not exists")
1846
+ elif not path:
1847
+ path = os.getcwd()
1848
+ path = os.path.join(path, project, "platform")
1849
+
1850
+ compose_path = os.path.join(path, "docker-compose.yaml")
1851
+ if not os.path.exists(compose_path):
1852
+ raise ValueError(f"{compose_path} not exists")
1853
+
1854
+ for layer in COMPONENTS:
1855
+ subprocess.run(
1856
+ ["docker", "compose", "--profile", f"{layer}", "stop"], cwd=path
1857
+ )
1858
+
1859
+ except Exception as e:
1860
+ rprint(f"[bold red] {e} [/bold red]")
1861
+
1862
+
1863
+ @app.command()
1864
+ def status(project: str = "", path: str = ""):
1865
+ """Run `docker compose ps`."""
1866
+ try:
1867
+ if path and not os.path.exists(path):
1868
+ raise ValueError(f"{path} does not exists")
1869
+ elif not path:
1870
+ path = os.getcwd()
1871
+ path = os.path.join(path, project, "platform")
1872
+
1873
+ compose_path = os.path.join(path, "docker-compose.yaml")
1874
+ if not os.path.exists(compose_path):
1875
+ raise ValueError(f"{compose_path} not exists")
1876
+
1877
+ subprocess.run(["docker", "compose", "ps", "--all"], cwd=path)
1878
+
1879
+ except Exception as e:
1880
+ rprint(f"[bold red] {e} [/bold red]")
1881
+
1882
+
1883
+ @app.command()
1884
+ def setup(project: str = "", path: str = ""):
1885
+ """Run `platform/setup.sh`."""
1886
+ try:
1887
+ if path and not os.path.exists(path):
1888
+ raise ValueError(f"{path} does not exists")
1889
+ elif not path:
1890
+ path = os.getcwd()
1891
+ path = os.path.join(path, project, "platform")
1892
+
1893
+ setup_path = os.path.join(path, "setup.sh")
1894
+ if not os.path.exists(setup_path):
1895
+ raise ValueError(f"{setup_path} not exists")
1896
+
1897
+ os.chmod(setup_path, os.stat(setup_path).st_mode | stat.S_IEXEC)
1898
+ subprocess.run(["./setup.sh"], cwd=path)
1899
+
1900
+ except Exception as e:
1901
+ rprint(f"[bold red] {e} [/bold red]")
1902
+
1903
+
1904
+ @app.command()
1905
+ def check(project: str = "", path: str = ""):
1906
+ """Run `pre-commit run --all-files`."""
1907
+ try:
1908
+ if path and not os.path.exists(path):
1909
+ raise ValueError(f"{path} does not exist")
1910
+ elif not path:
1911
+ path = os.getcwd()
1912
+ path = os.path.join(path, project)
1913
+ venv_path = os.path.join(path, ".venv")
1914
+ if not os.path.exists(venv_path):
1915
+ raise ValueError(f"{venv_path} does not exist")
1916
+
1917
+ pre_commit_path = os.path.join(path, ".pre-commit-config.yaml")
1918
+ if not os.path.exists(pre_commit_path):
1919
+ raise ValueError(f"{pre_commit_path} does not exist")
1920
+
1921
+ subprocess.run([".venv/bin/pre-commit", "run", "--all-files"], cwd=path)
1922
+ except Exception as e:
1923
+ rprint(f"[bold red] {e} [/bold red]")
1924
+
1925
+
1926
+ @app.command()
1927
+ def docs(project: str = "", path: str = ""):
1928
+ """Run `mkdocs serve`."""
1929
+ try:
1930
+ if path and not os.path.exists(path):
1931
+ raise ValueError(f"{path} does not exist")
1932
+ elif not path:
1933
+ path = os.getcwd()
1934
+ path = os.path.join(path, project)
1935
+ venv_path = os.path.join(path, ".venv")
1936
+ if not os.path.exists(venv_path):
1937
+ raise ValueError(f"{venv_path} does not exist")
1938
+
1939
+ docs_path = os.path.join(path, "mkdocs.yml")
1940
+ if not os.path.exists(docs_path):
1941
+ raise ValueError(f"{docs_path} does not exist")
1942
+
1943
+ subprocess.run([".venv/bin/mkdocs", "serve"], cwd=path)
1944
+ except Exception as e:
1945
+ rprint(f"[bold red] {e} [/bold red]")
1946
+
1947
+
1948
+ @app.command()
1949
+ def test(project: str = "", path: str = "", cov: str = ""):
1950
+ """Run `pytest --cov={cov} tests`."""
1951
+ try:
1952
+ if path and not os.path.exists(path):
1953
+ raise ValueError(f"{path} does not exist")
1954
+ elif not path:
1955
+ path = os.getcwd()
1956
+ path = os.path.join(path, project)
1957
+ venv_path = os.path.join(path, ".venv")
1958
+ if not os.path.exists(venv_path):
1959
+ raise ValueError(f"{venv_path} does not exist")
1960
+
1961
+ test_path = os.path.join(path, "tests")
1962
+ if not os.path.exists(test_path):
1963
+ raise ValueError(f"{test_path} does not exist")
1964
+
1965
+ if cov:
1966
+ cov_path = os.path.join(path, cov)
1967
+ if not os.path.exists(cov_path):
1968
+ raise ValueError(f"{cov_path} does not exist")
1969
+
1970
+ subprocess.run([".venv/bin/pytest", f"--cov={cov}", "tests"], cwd=path)
1971
+ else:
1972
+ subprocess.run([".venv/bin/pytest", "tests"], cwd=path)
1973
+ except Exception as e:
1974
+ rprint(f"[bold red] {e} [/bold red]")
1975
+
1976
+
1519
1977
  def main():
1520
1978
  """Main function which starts the app."""
1521
1979
  rprint(colorized_logo())