mirror of
https://gitlab.gnome.org/jpu/cambalache.git
synced 2025-08-19 00:03:34 -04:00
Initial commit
Supports type system and interface data model. Basic history (undo/redo stack) implementation with triggers.
This commit is contained in:
commit
51d4185cd8
8
COPYING
Normal file
8
COPYING
Normal file
@ -0,0 +1,8 @@
|
||||
Cambalache UI Maker
|
||||
|
||||
Copyright (C) 2020-2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
|
||||
Unauthorized copying of this project, via any medium is strictly prohibited.
|
||||
|
||||
This application is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# cambalache-db
|
||||
|
||||
Cambalache Data Model generation tool
|
||||
|
67
cambalache-db/cambalache-db-test.sql
Normal file
67
cambalache-db/cambalache-db-test.sql
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* CambalacheDB - Data Model for Cambalache
|
||||
*
|
||||
* Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
|
||||
*
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*/
|
||||
|
||||
/* Test model */
|
||||
|
||||
INSERT INTO type (type_id, parent) VALUES
|
||||
('GtkWidget', 'object'),
|
||||
('GtkWindow', 'GtkWidget'),
|
||||
('GtkImage', 'GtkWidget'),
|
||||
('GtkBox', 'GtkWidget'),
|
||||
('GtkLabel', 'GtkWidget'),
|
||||
('GtkButton', 'GtkWidget'),
|
||||
('GtkToggleButton', 'GtkButton');
|
||||
|
||||
INSERT INTO property (owner_id, property_id, type_id) VALUES
|
||||
('GtkWidget', 'name', 'string'),
|
||||
('GtkWidget', 'parent', 'GtkWidget'),
|
||||
('GtkImage', 'file', 'string'),
|
||||
('GtkBox', 'orientation', 'enum'),
|
||||
('GtkLabel', 'label', 'string'),
|
||||
('GtkButton', 'label', 'string'),
|
||||
('GtkButton', 'image', 'GtkImage'),
|
||||
('GtkToggleButton', 'active', 'boolean');
|
||||
|
||||
INSERT INTO child_property (owner_id, property_id, type_id) VALUES
|
||||
('GtkBox', 'position', 'int'),
|
||||
('GtkBox', 'expand', 'boolean'),
|
||||
('GtkBox', 'fill', 'boolean');
|
||||
|
||||
|
||||
INSERT INTO signal (owner_id, signal_id) VALUES
|
||||
('GtkWidget', 'event'),
|
||||
('GtkBox', 'add'),
|
||||
('GtkBox', 'remove'),
|
||||
('GtkButton', 'clicked'),
|
||||
('GtkToggleButton', 'toggled');
|
||||
|
||||
/* Test Project */
|
||||
|
||||
INSERT INTO object (type_id, name, parent_id) VALUES
|
||||
('GtkWindow', 'main', NULL),
|
||||
('GtkBox', 'box', 1),
|
||||
('GtkLabel', 'label', 2),
|
||||
('GtkButton', 'button', 2);
|
||||
|
||||
INSERT INTO object_property (object_id, owner_id, property_id, value) VALUES
|
||||
(3, 'GtkLabel', 'label', 'Hello World'),
|
||||
(4, 'GtkButton', 'label', 'Click Me');
|
||||
|
||||
INSERT INTO object_child_property (object_id, child_id, owner_id, property_id, value) VALUES
|
||||
(1, 3, 'GtkBox', 'position', 1),
|
||||
(1, 3, 'GtkBox', 'expand', 1),
|
||||
(1, 4, 'GtkBox', 'position', 2),
|
||||
(1, 4, 'GtkBox', 'fill', 0);
|
||||
|
||||
INSERT INTO object_signal (object_id, owner_id, signal_id, handler) VALUES
|
||||
(4, 'GtkButton', 'clicked', 'on_button_clicked');
|
||||
|
||||
INSERT INTO interface (name, filename) VALUES ('Test UI', 'test.ui');
|
||||
|
||||
INSERT INTO interface_object (interface_id, object_id) VALUES (1, 1);
|
||||
|
124
cambalache-db/cambalache-db.py
Normal file
124
cambalache-db/cambalache-db.py
Normal file
@ -0,0 +1,124 @@
|
||||
#
|
||||
# CambalacheDB - Data Model for Cambalache
|
||||
#
|
||||
# Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
import sys
|
||||
import sqlite3
|
||||
|
||||
|
||||
def db_create_history_table(c, table):
|
||||
# Create Table
|
||||
c.executescript(f'''
|
||||
CREATE TABLE history_{table} AS SELECT * FROM {table} WHERE 0;
|
||||
ALTER TABLE history_{table} ADD COLUMN history_id INTERGER REFERENCES history;
|
||||
''')
|
||||
|
||||
# Get table columns
|
||||
columns = None
|
||||
old_values = None
|
||||
new_values = None
|
||||
|
||||
pk_columns = None
|
||||
pk_columns_values = None
|
||||
|
||||
for row in c.execute(f'PRAGMA table_info({table});'):
|
||||
col = row[1]
|
||||
col_type = row[2]
|
||||
pk = row[5]
|
||||
|
||||
if columns == None:
|
||||
columns = col
|
||||
old_values = 'OLD.' + col
|
||||
new_values = 'NEW.' + col
|
||||
else:
|
||||
columns += ', ' + col
|
||||
old_values += ', OLD.' + col
|
||||
new_values += ', NEW.' + col
|
||||
|
||||
if pk:
|
||||
if pk_columns == None:
|
||||
pk_columns = col
|
||||
pk_columns_values = 'NEW.' + col
|
||||
else:
|
||||
pk_columns += ', ' + col
|
||||
pk_columns_values += ', NEW.' + col
|
||||
|
||||
|
||||
# Create history triggers
|
||||
|
||||
# INSERT Trigger
|
||||
c.execute(f'''
|
||||
CREATE TRIGGER on_{table}_insert AFTER INSERT ON {table}
|
||||
BEGIN
|
||||
INSERT INTO history (command, table_name) VALUES ('INSERT', '{table}');
|
||||
INSERT INTO history_{table} (history_id, {columns})
|
||||
VALUES (last_insert_rowid(), {new_values});
|
||||
END;
|
||||
''')
|
||||
|
||||
# DELETE Trigger
|
||||
c.execute(f'''
|
||||
CREATE TRIGGER on_{table}_delete AFTER DELETE ON {table}
|
||||
BEGIN
|
||||
INSERT INTO history (command, table_name) VALUES ('DELETE', '{table}');
|
||||
INSERT INTO history_{table} (history_id, {columns})
|
||||
VALUES (last_insert_rowid(), {old_values});
|
||||
END;
|
||||
''')
|
||||
|
||||
# UPDATE Trigger
|
||||
c.execute(f'''
|
||||
CREATE TRIGGER on_{table}_update AFTER UPDATE ON {table}
|
||||
BEGIN
|
||||
INSERT INTO history (command, table_name) VALUES ('UPDATE', '{table}');
|
||||
INSERT INTO history_{table} (history_id, {columns})
|
||||
VALUES (last_insert_rowid(), {new_values});
|
||||
END;
|
||||
''')
|
||||
|
||||
|
||||
def db_create_history_tables(conn):
|
||||
c = conn.cursor()
|
||||
db_create_history_table(c, 'object')
|
||||
db_create_history_table(c, 'object_property')
|
||||
db_create_history_table(c, 'object_child_property')
|
||||
db_create_history_table(c, 'object_signal')
|
||||
db_create_history_table(c, 'interface')
|
||||
db_create_history_table(c, 'interface_object')
|
||||
conn.commit()
|
||||
|
||||
|
||||
def row_diff_count(*args):
|
||||
return len(args)
|
||||
|
||||
|
||||
def db_create(filename):
|
||||
# Create DB file
|
||||
conn = sqlite3.connect(filename)
|
||||
|
||||
conn.create_function("row_diff_count", -1, row_diff_count, deterministic=True)
|
||||
|
||||
# Create DB tables
|
||||
with open('cambalache-db.sql', 'r') as sql:
|
||||
c = conn.cursor()
|
||||
c.executescript(sql.read())
|
||||
conn.commit()
|
||||
sql.close()
|
||||
|
||||
db_create_history_tables(conn)
|
||||
|
||||
return conn
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Ussage: {sys.argv[0]} database.sqlite")
|
||||
exit()
|
||||
|
||||
conn = db_create(sys.argv[1])
|
||||
conn.commit()
|
||||
conn.close()
|
205
cambalache-db/cambalache-db.sql
Normal file
205
cambalache-db/cambalache-db.sql
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* CambalacheDB - Data Model for Cambalache
|
||||
*
|
||||
* Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
|
||||
*
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*/
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
/** Common Data Model **/
|
||||
|
||||
CREATE TABLE license (
|
||||
license_id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
license_text TEXT NOT NULL
|
||||
);
|
||||
|
||||
|
||||
/* Type
|
||||
*
|
||||
* Base table to keep type information
|
||||
*/
|
||||
CREATE TABLE type (
|
||||
type_id TEXT PRIMARY KEY,
|
||||
|
||||
parent TEXT REFERENCES type,
|
||||
get_type TEXT,
|
||||
version TEXT,
|
||||
deprecated_version TEXT
|
||||
);
|
||||
|
||||
|
||||
/* Add fundamental types */
|
||||
INSERT INTO type (type_id) VALUES
|
||||
('char'), ('uchar'), ('boolean'), ('int'), ('uint'), ('long'), ('ulong'),
|
||||
('int64'), ('uint64'), ('enum'), ('flags'), ('float'), ('double'), ('string'),
|
||||
('pointer'), ('boxed'), ('param'), ('object'), ('gtype'), ('variant');
|
||||
|
||||
|
||||
/* Property
|
||||
*
|
||||
* TODO: Add check to make sure property:owner_id is not fundamental
|
||||
*/
|
||||
CREATE TABLE property (
|
||||
owner_id TEXT REFERENCES type,
|
||||
property_id TEXT NOT NULL,
|
||||
|
||||
type_id TEXT REFERENCES type,
|
||||
writable BOOLEAN,
|
||||
construct_only BOOLEAN,
|
||||
default_value TEXT,
|
||||
version TEXT,
|
||||
deprecated_version TEXT,
|
||||
PRIMARY KEY(owner_id, property_id)
|
||||
);
|
||||
|
||||
CREATE TRIGGER property_check_owner_id BEFORE INSERT ON property
|
||||
BEGIN
|
||||
SELECT
|
||||
CASE
|
||||
WHEN (SELECT parent FROM type WHERE type_id=NEW.owner_id) IS NULL THEN
|
||||
RAISE (ABORT,'owner_id is not an object type')
|
||||
END;
|
||||
END;
|
||||
|
||||
|
||||
/* Child Property
|
||||
*
|
||||
*/
|
||||
CREATE TABLE child_property (
|
||||
owner_id TEXT REFERENCES type,
|
||||
property_id TEXT NOT NULL,
|
||||
|
||||
type_id TEXT REFERENCES type,
|
||||
writable BOOLEAN,
|
||||
construct_only BOOLEAN,
|
||||
default_value TEXT,
|
||||
version TEXT,
|
||||
deprecated_version TEXT,
|
||||
PRIMARY KEY(owner_id, property_id)
|
||||
);
|
||||
|
||||
|
||||
/* Signal
|
||||
*
|
||||
* TODO: Add check to make sure signal:owner_type is not fundamental
|
||||
*/
|
||||
CREATE TABLE signal (
|
||||
owner_id TEXT REFERENCES type(type_id),
|
||||
signal_id TEXT NOT NULL,
|
||||
|
||||
version TEXT,
|
||||
deprecated_version TEXT,
|
||||
PRIMARY KEY(owner_id, signal_id)
|
||||
);
|
||||
|
||||
|
||||
/** Project Data Model **/
|
||||
|
||||
/* Object
|
||||
*
|
||||
*/
|
||||
CREATE TABLE object (
|
||||
object_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
type_id TEXT NOT NULL REFERENCES type,
|
||||
name TEXT UNIQUE,
|
||||
parent_id INTEGER REFERENCES object
|
||||
);
|
||||
|
||||
|
||||
/* Object Property
|
||||
*
|
||||
*/
|
||||
CREATE TABLE object_property (
|
||||
object_id INTEGER REFERENCES object,
|
||||
owner_id TEXT,
|
||||
property_id TEXT,
|
||||
|
||||
value TEXT,
|
||||
translatable BOOLEAN,
|
||||
PRIMARY KEY(object_id, owner_id, property_id),
|
||||
FOREIGN KEY(owner_id, property_id) REFERENCES property
|
||||
);
|
||||
|
||||
|
||||
/* Object Child Property
|
||||
*
|
||||
*/
|
||||
CREATE TABLE object_child_property (
|
||||
object_id INTEGER REFERENCES object,
|
||||
child_id INTEGER REFERENCES object,
|
||||
owner_id TEXT,
|
||||
property_id TEXT,
|
||||
|
||||
value TEXT,
|
||||
translatable BOOLEAN,
|
||||
PRIMARY KEY(object_id, child_id, owner_id, property_id),
|
||||
FOREIGN KEY(owner_id, property_id) REFERENCES child_property
|
||||
);
|
||||
|
||||
|
||||
/* Object Signal
|
||||
*
|
||||
*/
|
||||
CREATE TABLE object_signal (
|
||||
object_id INTEGER REFERENCES object,
|
||||
owner_id TEXT,
|
||||
signal_id TEXT,
|
||||
|
||||
handler TEXT NOT NULL,
|
||||
detail TEXT,
|
||||
user_data INTEGER REFERENCES object,
|
||||
swap BOOLEAN,
|
||||
after BOOLEAN,
|
||||
PRIMARY KEY(object_id, owner_id, signal_id),
|
||||
FOREIGN KEY(owner_id, signal_id) REFERENCES signal
|
||||
);
|
||||
|
||||
|
||||
/* Interface
|
||||
*
|
||||
*/
|
||||
CREATE TABLE interface (
|
||||
interface_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
name TEXT,
|
||||
description TEXT,
|
||||
copyright TEXT,
|
||||
authors TEXT,
|
||||
license_id TEXT REFERENCES license,
|
||||
filename TEXT,
|
||||
translation_domain TEXT
|
||||
);
|
||||
|
||||
|
||||
/* Interface Object
|
||||
*
|
||||
* TODO: check objects are toplevels (have no parent)
|
||||
*/
|
||||
CREATE TABLE interface_object (
|
||||
interface_id INTEGER REFERENCES interface,
|
||||
object_id INTEGER REFERENCES object,
|
||||
|
||||
template TEXT,
|
||||
PRIMARY KEY(interface_id, object_id)
|
||||
);
|
||||
|
||||
/*
|
||||
* Implement undo/redo stack with triggers
|
||||
*
|
||||
* We should be able to store the whole project history if we want to.
|
||||
*
|
||||
* history_* tables and triggers are auto generated to avoid copy/paste errors
|
||||
*/
|
||||
|
||||
/* Main history table */
|
||||
|
||||
CREATE TABLE history (
|
||||
history_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
command TEXT,
|
||||
table_name TEXT
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user