2
2
import tkinter as tk
3
3
from tkinter import messagebox , ttk
4
4
import tkinter .filedialog as filedialog
5
+ import tkinter .messagebox as messagebox
5
6
import os
6
7
import subprocess
7
8
8
9
from dotenv import load_dotenv
9
10
from git import Repo
10
11
import socket
12
+ import json
13
+ from jsonschema import validate , ValidationError
11
14
12
15
load_dotenv ()
13
16
17
20
file_extensions = os .getenv ("file_extensions" )
18
21
search_folders = os .getenv ("search_folders" )
19
22
repo_path = './repo'
23
+ json_schema_path = "./json-schemas"
24
+
20
25
21
26
class GitGUIApp :
22
27
def __init__ (self , root ):
23
28
self .root = root
24
29
self .root .title (app_name )
25
30
31
+ self .json_schemas = self .load_json_schemas (json_schema_path )
32
+
26
33
# Frames
27
34
left_frame = tk .Frame (root )
28
35
left_frame .pack (side = tk .TOP , padx = 10 , pady = 10 , fill = tk .X )
@@ -82,6 +89,15 @@ def get_files(self):
82
89
83
90
self .populate_list (repo_path )
84
91
92
+ def load_json_schemas (self , schema_dir ):
93
+ schemas = {}
94
+ # todo: check if folder exist
95
+ for file in os .listdir (schema_dir ):
96
+ if file .endswith ('.json' ):
97
+ with open (os .path .join (schema_dir , file )) as schema_file :
98
+ schemas [file ] = json .load (schema_file )
99
+ return schemas
100
+
85
101
def populate_list (self , repo_path ):
86
102
extensions = os .environ .get ('file_extensions' , '' )
87
103
valid_extensions = extensions .split (';' )
@@ -129,6 +145,17 @@ def on_file_click(self, event):
129
145
filename = self .file_list .item (item , 'values' )[1 ]
130
146
file_path = os .path .join (repo_path , filename )
131
147
148
+ if filename .endswith ('.json' ):
149
+ with open (file_path ) as json_file :
150
+ json_data = json .load (json_file )
151
+ for _ , schema in self .json_schemas .items ():
152
+ try :
153
+ validate (instance = json_data , schema = schema )
154
+ self .show_json_dialog (json_data , schema , file_path )
155
+ return
156
+ except ValidationError :
157
+ pass
158
+
132
159
if os .path .isfile (file_path ):
133
160
if platform .system () == "Windows" :
134
161
os .startfile (file_path )
@@ -137,6 +164,112 @@ def on_file_click(self, event):
137
164
else : # Linux
138
165
subprocess .call (["xdg-open" , file_path ])
139
166
167
+ def show_json_dialog (self , json_data , schema , file_path ):
168
+ dialog = tk .Toplevel (self .root )
169
+ dialog .title (schema .get ('title' , 'JSON Data' ))
170
+
171
+ # Create a canvas and a scrollbar
172
+ canvas = tk .Canvas (dialog )
173
+ scrollbar = tk .Scrollbar (dialog , orient = "vertical" , command = canvas .yview )
174
+ scrollable_frame = tk .Frame (canvas )
175
+
176
+ # Configure canvas
177
+ canvas .create_window ((0 , 0 ), window = scrollable_frame , anchor = "nw" )
178
+ canvas .configure (yscrollcommand = scrollbar .set )
179
+
180
+ def on_configure (event ):
181
+ canvas .configure (scrollregion = canvas .bbox ("all" ))
182
+
183
+ scrollable_frame .bind ("<Configure>" , on_configure )
184
+
185
+ self .widget_references = {}
186
+
187
+ row = 0
188
+ for prop , details in schema .get ('properties' , {}).items ():
189
+ label = tk .Label (scrollable_frame , text = prop )
190
+ label .grid (row = row , column = 0 , sticky = 'ew' , padx = 10 , pady = 5 )
191
+
192
+ value = json_data .get (prop , '' )
193
+
194
+ if isinstance (value , dict ):
195
+ nested_row = 0
196
+ frame = tk .Frame (scrollable_frame )
197
+ frame .grid (row = row , column = 1 , padx = 10 , pady = 5 , sticky = 'ew' )
198
+ for nested_prop , nested_details in details ['properties' ].items ():
199
+ nested_label = tk .Label (frame , text = nested_prop )
200
+ nested_label .grid (row = nested_row , column = 0 , sticky = 'w' , padx = 10 , pady = 2 )
201
+ nested_text = tk .Text (frame , height = 3 , wrap = 'word' )
202
+ nested_text .insert ('end' , str (value .get (nested_prop , '' )))
203
+ nested_text .grid (row = nested_row , column = 1 , padx = 10 , pady = 2 , sticky = 'ew' )
204
+ frame .grid_columnconfigure (1 , weight = 1 )
205
+ nested_row += 1
206
+ self .widget_references [prop ] = nested_text
207
+ else :
208
+ text = tk .Text (scrollable_frame , height = 3 , wrap = 'word' )
209
+ text .insert ('end' , str (value ))
210
+ text .grid (row = row , column = 1 , padx = 10 , pady = 5 , sticky = 'ew' )
211
+ scrollable_frame .grid_columnconfigure (1 , weight = 1 )
212
+ self .widget_references [prop ] = text
213
+
214
+ row += 1
215
+
216
+ # Save button
217
+ save_button = tk .Button (dialog , text = "Save" , command = lambda : self .save_json_data (json_data , schema , file_path ))
218
+ save_button .pack ()
219
+
220
+ canvas .pack (side = "left" , fill = "both" , expand = True )
221
+ scrollbar .pack (side = "right" , fill = "y" )
222
+
223
+ def save_json_data (self , original_data , schema , file_path ):
224
+ def parse_input (data , schema_properties , parent = None ):
225
+ for prop , details in schema_properties .items ():
226
+ if details .get ('type' ) == 'object' :
227
+ # Handle nested object
228
+ nested_data = data .get (prop , {})
229
+ parse_input (nested_data , details ['properties' ], prop )
230
+ data [prop ] = nested_data
231
+ else :
232
+ widget_key = f"{ parent } .{ prop } " if parent else prop
233
+ widget = self .widget_references .get (widget_key )
234
+
235
+ if widget :
236
+ input_value = widget .get ("1.0" , "end-1c" ) # Get text from Text widget
237
+
238
+ # Convert input value to the correct type based on the schema
239
+ if details .get ('type' ) == 'number' :
240
+ try :
241
+ input_value = float (input_value )
242
+ except ValueError :
243
+ messagebox .showerror ("Validation Error" , f"Value for '{ prop } ' should be a number." )
244
+ return False
245
+ elif details .get ('type' ) == 'integer' :
246
+ try :
247
+ input_value = int (input_value )
248
+ except ValueError :
249
+ messagebox .showerror ("Validation Error" , f"Value for '{ prop } ' should be a integer." )
250
+ return False
251
+ # Add other type conversions as needed
252
+
253
+ data [prop ] = input_value
254
+ return True
255
+
256
+ # Create a copy of the original data to modify
257
+ updated_data = original_data .copy ()
258
+
259
+ if not parse_input (updated_data , schema .get ('properties' , {})):
260
+ return # Return early if validation fails
261
+
262
+ # Validate updated data
263
+ try :
264
+ validate (instance = updated_data , schema = schema )
265
+ # Write back to file if validation passes
266
+ with open (file_path , 'w' ) as json_file :
267
+ json .dump (updated_data , json_file , indent = 4 )
268
+ messagebox .showinfo ("Success" , "Data saved successfully." )
269
+ except ValidationError as e :
270
+ messagebox .showerror ("Validation Error" , str (e ))
271
+
272
+
140
273
def save_files (self ):
141
274
if not os .path .exists (repo_path ):
142
275
messagebox .showerror ("Error" , "Repository not cloned." )
0 commit comments