eksa-framework 1.1.1 โ 2.2.1
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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/Gemfile.lock +54 -0
- data/README.md +48 -50
- data/app/models/pesan.rb +10 -0
- data/app/views/docs.html.erb +11 -5
- data/app/views/index.html.erb +1 -1
- data/config.ru +6 -5
- data/exe/eksa +146 -0
- data/lib/eksa/controller.rb +17 -1
- data/lib/eksa/model.rb +17 -12
- data/lib/eksa/version.rb +1 -1
- data/lib/eksa.rb +70 -1
- data/spec/application_spec.rb +29 -0
- data/spec/model_spec.rb +13 -0
- data/spec/spec_helper.rb +22 -0
- metadata +11 -5
- data/db/eksa_app.db +0 -0
- data/exe/eksa-init +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba5e76573384bd8808a04847817e7237efc4dc6230d05b7c6ca732d7de32608b
|
|
4
|
+
data.tar.gz: 786e3dd03a18afd6f4ed632c12b52d01339136dade7575789c9c355bb086829e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fbbabdc3eb08873aca77b657ae5c1d56590738b7b89618276e841189c810a46c0fcfc459e7aef8ed78495a0739363bbb474de7c17cb620cbe675cfd2a78a8863
|
|
7
|
+
data.tar.gz: 7fba997f5684df20931e3fb297d6d8cdb73c9f0b174d765b6abcfb95c60b491fbf8608ebfb60b39998e6fc8e8420000bf8518829966c132f22aae716ecbf3b6d
|
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
diff-lcs (1.6.2)
|
|
5
|
+
nio4r (2.7.5)
|
|
6
|
+
puma (7.2.0)
|
|
7
|
+
nio4r (~> 2.0)
|
|
8
|
+
rack (3.2.5)
|
|
9
|
+
rack-test (2.2.0)
|
|
10
|
+
rack (>= 1.3)
|
|
11
|
+
rackup (2.3.1)
|
|
12
|
+
rack (>= 3)
|
|
13
|
+
rspec (3.13.2)
|
|
14
|
+
rspec-core (~> 3.13.0)
|
|
15
|
+
rspec-expectations (~> 3.13.0)
|
|
16
|
+
rspec-mocks (~> 3.13.0)
|
|
17
|
+
rspec-core (3.13.6)
|
|
18
|
+
rspec-support (~> 3.13.0)
|
|
19
|
+
rspec-expectations (3.13.5)
|
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
21
|
+
rspec-support (~> 3.13.0)
|
|
22
|
+
rspec-mocks (3.13.8)
|
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
24
|
+
rspec-support (~> 3.13.0)
|
|
25
|
+
rspec-support (3.13.7)
|
|
26
|
+
sqlite3 (2.9.1-x86_64-linux-gnu)
|
|
27
|
+
|
|
28
|
+
PLATFORMS
|
|
29
|
+
x86_64-linux-gnu
|
|
30
|
+
|
|
31
|
+
DEPENDENCIES
|
|
32
|
+
puma
|
|
33
|
+
rack
|
|
34
|
+
rack-test (~> 2.1)
|
|
35
|
+
rackup
|
|
36
|
+
rspec (~> 3.12)
|
|
37
|
+
sqlite3
|
|
38
|
+
|
|
39
|
+
CHECKSUMS
|
|
40
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
41
|
+
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
|
|
42
|
+
puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8
|
|
43
|
+
rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
|
|
44
|
+
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
|
|
45
|
+
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
|
|
46
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
47
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
48
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
49
|
+
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
50
|
+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
51
|
+
sqlite3 (2.9.1-x86_64-linux-gnu) sha256=1cbb644204ed143e5c96f6d59b5c571ba6f18b18a9dc5aa11c101187ff227afd
|
|
52
|
+
|
|
53
|
+
BUNDLED WITH
|
|
54
|
+
4.0.4
|
data/README.md
CHANGED
|
@@ -12,97 +12,95 @@
|
|
|
12
12
|
## ๐ Fitur Unggulan
|
|
13
13
|
|
|
14
14
|
* ๐ **Modern Glassmorphism UI**: Tampilan transparan yang indah dengan Tailwind CSS & Lucide Icons.
|
|
15
|
-
* โก **Rack 3
|
|
16
|
-
* ๐ ๏ธ **CLI
|
|
17
|
-
* ๐พ **
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* ๐ **Dynamic SEO Engine**: Penanganan otomatis file `robots.txt` dan `sitemap.xml
|
|
21
|
-
*
|
|
15
|
+
* โก **Rack 3 & Middleware Support**: Mendukung standar terbaru dan pembuatan pipeline middleware kustom.
|
|
16
|
+
* ๐ ๏ธ **Powerful CLI**: Inisialisasi project (`eksa init`), jalankan server (`eksa run`), generate komponen, dan **auto-routing** otomatis.
|
|
17
|
+
* ๐พ **Dynamic Database Engine**: Database SQLite otomatis dengan schema yang ditentukan oleh model Anda sendiri.
|
|
18
|
+
* ๐งช **Built-in Testing**: Lingkungan pengujian otomatis siap pakai menggunakan RSpec dan `rack-test`.
|
|
19
|
+
* ๐จ **Asset Helpers**: Library bawaan untuk pengelolaan CSS dan JS yang lebih rapi.
|
|
20
|
+
* ๐ **Dynamic SEO Engine**: Penanganan otomatis file `robots.txt` dan `sitemap.xml`.
|
|
21
|
+
* ๐ป **Aesthetic Error Pages**: Halaman 404 dengan desain Glassmorphism yang elegan secara native.
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
## ๐ ๏ธ Instalasi Cepat
|
|
26
26
|
|
|
27
27
|
### 1. Install via Gem
|
|
28
|
-
Anda bisa menginstal framework ini langsung dengan `gem`:
|
|
29
|
-
|
|
30
28
|
```bash
|
|
31
29
|
gem install eksa-framework
|
|
32
30
|
```
|
|
33
31
|
|
|
34
32
|
### 2. Inisialisasi Project Baru
|
|
35
|
-
|
|
36
|
-
Masuk ke folder baru, lalu jalankan generator Eksa:
|
|
37
|
-
|
|
38
33
|
```bash
|
|
39
|
-
mkdir my-
|
|
40
|
-
|
|
41
|
-
eksa-init
|
|
34
|
+
mkdir my-app && cd my-app
|
|
35
|
+
eksa init
|
|
42
36
|
```
|
|
43
37
|
|
|
44
38
|
### 3. Jalankan Server
|
|
45
|
-
|
|
46
39
|
```bash
|
|
47
40
|
bundle install
|
|
48
|
-
|
|
41
|
+
eksa run
|
|
49
42
|
```
|
|
50
43
|
|
|
51
|
-
Buka browser dan akses `http://localhost:9292`.
|
|
52
|
-
|
|
53
44
|
---
|
|
54
45
|
|
|
55
46
|
## ๐ป Panduan Pengembangan
|
|
56
47
|
|
|
57
|
-
### 1.
|
|
58
|
-
|
|
59
|
-
Eksa memisahkan data dari logika aplikasi dengan folder `db` di level root:
|
|
60
|
-
|
|
61
|
-
* `app/`: Logic & Views (Controllers, Models, Views).
|
|
62
|
-
* `db/`: File database SQLite `eksa_app.db` (Terpisah dari app).
|
|
63
|
-
* `lib/eksa/`: Core Engine (The magic happens here).
|
|
64
|
-
* `public/`: File statis (CSS, Images, JS).
|
|
65
|
-
|
|
66
|
-
### 2. Menambah Route & Controller
|
|
67
|
-
|
|
68
|
-
Daftarkan rute di `config.ru`:
|
|
48
|
+
### 1. Konfigurasi Aplikasi (`config.ru`)
|
|
49
|
+
Eksa kini menggunakan blok inisialisasi untuk konfigurasi yang lebih fleksibel:
|
|
69
50
|
|
|
70
51
|
```ruby
|
|
71
|
-
app.
|
|
52
|
+
app = Eksa::Application.new do |config|
|
|
53
|
+
config.config[:db_path] = "./db/production.db"
|
|
54
|
+
|
|
55
|
+
config.use Rack::Static, urls: ["/css", "/img"], root: "public"
|
|
56
|
+
config.use Rack::ShowExceptions
|
|
57
|
+
end
|
|
72
58
|
```
|
|
73
59
|
|
|
74
|
-
|
|
60
|
+
### 2. CLI Generator
|
|
61
|
+
Hemat waktu dengan menggunakan generator bawaan:
|
|
75
62
|
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
```bash
|
|
64
|
+
# Membuat controller dan view template
|
|
65
|
+
eksa g controller Blog
|
|
66
|
+
|
|
67
|
+
# Membuat model dan schema database
|
|
68
|
+
eksa g model Post
|
|
80
69
|
```
|
|
81
70
|
|
|
82
71
|
### 3. Database & Model
|
|
83
|
-
|
|
84
|
-
Eksa akan otomatis membuat tabel saat Anda mendefinisikannya di `lib/eksa/model.rb`. Untuk menggunakan data di controller:
|
|
72
|
+
Definisikan schema tabel Anda langsung di dalam model:
|
|
85
73
|
|
|
86
74
|
```ruby
|
|
87
|
-
|
|
75
|
+
class Post < Eksa::Model
|
|
76
|
+
def self.setup_schema
|
|
77
|
+
db.execute <<~SQL
|
|
78
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
79
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
80
|
+
title TEXT,
|
|
81
|
+
content TEXT
|
|
82
|
+
)
|
|
83
|
+
SQL
|
|
84
|
+
end
|
|
85
|
+
end
|
|
88
86
|
```
|
|
89
87
|
|
|
90
|
-
|
|
88
|
+
### 4. Asset Helpers
|
|
89
|
+
Gunakan helper di dalam view untuk menyisipkan asset:
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
```erb
|
|
92
|
+
<%= stylesheet_tag "style" %>
|
|
93
|
+
<%= javascript_tag "app" %>
|
|
94
|
+
```
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
### 5. Menjalankan Test
|
|
97
|
+
Pastikan aplikasi Anda berjalan dengan benar menggunakan RSpec:
|
|
95
98
|
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
background: rgba(255, 255, 255, 0.05);
|
|
99
|
-
backdrop-filter: blur(16px);
|
|
100
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
101
|
-
}
|
|
99
|
+
```bash
|
|
100
|
+
bundle exec rspec
|
|
102
101
|
```
|
|
103
102
|
|
|
104
103
|
---
|
|
105
104
|
|
|
106
105
|
## ๐ Lisensi
|
|
107
|
-
|
|
108
106
|
Proyek ini dilisensikan di bawah **MIT License**. Lihat file [LICENSE](LICENSE) untuk detail lebih lanjut.
|
data/app/models/pesan.rb
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
class Pesan < Eksa::Model
|
|
2
|
+
def self.setup_schema
|
|
3
|
+
db.execute <<-SQL
|
|
4
|
+
CREATE TABLE IF NOT EXISTS pesan (
|
|
5
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6
|
+
konten TEXT,
|
|
7
|
+
pengirim TEXT
|
|
8
|
+
);
|
|
9
|
+
SQL
|
|
10
|
+
end
|
|
11
|
+
|
|
2
12
|
def self.semua
|
|
3
13
|
db.execute("SELECT * FROM pesan ORDER BY id DESC")
|
|
4
14
|
end
|
data/app/views/docs.html.erb
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
<div>
|
|
46
46
|
<p class="text-white/30"># 2. Inisialisasi project baru</p>
|
|
47
|
-
<p class="text-indigo-300">eksa
|
|
47
|
+
<p class="text-indigo-300">eksa init</p>
|
|
48
48
|
</div>
|
|
49
49
|
<div>
|
|
50
50
|
<p class="text-white/30"># 3. Install dependensi</p>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
<div>
|
|
54
54
|
<p class="text-white/30"># 4. Jalankan server</p>
|
|
55
|
-
<p class="text-indigo-300">
|
|
55
|
+
<p class="text-indigo-300">eksa run</p>
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
58
|
</section>
|
|
@@ -74,9 +74,13 @@
|
|
|
74
74
|
<code class="text-indigo-300 font-bold">lib/eksa/</code>
|
|
75
75
|
<p class="text-xs text-white/50 mt-1">Mesin inti (Core Engine) dari Eksa Framework.</p>
|
|
76
76
|
</div>
|
|
77
|
+
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
78
|
+
<code class="text-indigo-300 font-bold">spec/</code>
|
|
79
|
+
<p class="text-xs text-white/50 mt-1">Folder testing otomatis menggunakan RSpec (disertakan saat <code class="text-indigo-200">init</code>).</p>
|
|
80
|
+
</div>
|
|
77
81
|
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
78
82
|
<code class="text-indigo-300 font-bold">exe/</code>
|
|
79
|
-
<p class="text-xs text-white/50 mt-1">Executable files seperti
|
|
83
|
+
<p class="text-xs text-white/50 mt-1">Executable files seperti CLI <code class="text-indigo-200">eksa</code>.</p>
|
|
80
84
|
</div>
|
|
81
85
|
</div>
|
|
82
86
|
</section>
|
|
@@ -123,11 +127,13 @@
|
|
|
123
127
|
|
|
124
128
|
<section id="build" class="mb-12 scroll-mt-24">
|
|
125
129
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
126
|
-
<i data-lucide="package-check" class="w-6 h-6 text-indigo-400"></i> Build
|
|
130
|
+
<i data-lucide="package-check" class="w-6 h-6 text-indigo-400"></i> Build & Publish
|
|
127
131
|
</h2>
|
|
128
132
|
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
133
|
+
<p class="text-white/30"># Build gem lokal</p>
|
|
129
134
|
<p class="text-indigo-300">gem build eksa-framework.gemspec</p>
|
|
130
|
-
<p class="text-
|
|
135
|
+
<p class="text-white/30 mt-4"># Publish ke RubyGems / GitHub Packages</p>
|
|
136
|
+
<p class="text-indigo-300">gem push eksa-framework-1.2.1.gem</p>
|
|
131
137
|
</div>
|
|
132
138
|
</section>
|
|
133
139
|
|
data/app/views/index.html.erb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
|
|
3
3
|
<div>
|
|
4
4
|
<span class="inline-block bg-indigo-500/20 text-indigo-200 text-xs font-bold px-3 py-1 rounded-full mb-3 border border-indigo-500/30">
|
|
5
|
-
|
|
5
|
+
v1.2.1 Alpha
|
|
6
6
|
</span>
|
|
7
7
|
<h1 class="text-4xl font-extrabold tracking-tight">
|
|
8
8
|
Halo, <span class="text-indigo-300"><%= @nama %></span>!
|
data/config.ru
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
require './lib/eksa'
|
|
2
|
-
require './app/controllers/pages_controller'
|
|
3
|
-
|
|
4
|
-
use Rack::Static, urls: ["/css", "/img"], root: "public"
|
|
5
|
-
use Rack::ShowExceptions
|
|
6
2
|
|
|
7
3
|
Dir[File.join(__dir__, 'app/controllers/*.rb')].each { |file| require_relative file }
|
|
8
4
|
|
|
9
|
-
app = Eksa::Application.new
|
|
5
|
+
app = Eksa::Application.new do |config|
|
|
6
|
+
config.config[:db_path] = File.expand_path("./db/eksa_app.db")
|
|
7
|
+
|
|
8
|
+
config.use Rack::Static, urls: ["/css", "/img"], root: "public"
|
|
9
|
+
config.use Rack::ShowExceptions
|
|
10
|
+
end
|
|
10
11
|
|
|
11
12
|
app.add_route "/", PagesController, :index
|
|
12
13
|
app.add_route "/hapus", PagesController, :hapus_pesan
|
data/exe/eksa
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
def usage
|
|
7
|
+
puts "\nโจ Eksa Framework CLI โจ"
|
|
8
|
+
puts "-----------------------------"
|
|
9
|
+
puts "Usage:"
|
|
10
|
+
puts " eksa init - Inisialisasi project baru"
|
|
11
|
+
puts " eksa g controller NAME - Generate controller baru"
|
|
12
|
+
puts " eksa g model NAME - Generate model baru"
|
|
13
|
+
puts " eksa run - Jalankan server aplikasi"
|
|
14
|
+
puts "-----------------------------\n"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def init_project
|
|
18
|
+
gem_root = File.expand_path("../..", __FILE__)
|
|
19
|
+
target_dir = Dir.pwd
|
|
20
|
+
|
|
21
|
+
puts "๐ Menginisialisasi project di: #{target_dir}"
|
|
22
|
+
|
|
23
|
+
items_to_init = ['app', 'lib', 'public', 'db', 'spec', 'config.ru', 'Gemfile', 'README.md']
|
|
24
|
+
|
|
25
|
+
items_to_init.each do |item|
|
|
26
|
+
source = File.join(gem_root, item)
|
|
27
|
+
destination = File.join(target_dir, item)
|
|
28
|
+
|
|
29
|
+
if File.exist?(source)
|
|
30
|
+
if File.directory?(source)
|
|
31
|
+
FileUtils.cp_r(source, target_dir)
|
|
32
|
+
puts " [OK] Folder '#{item}' berhasil disalin."
|
|
33
|
+
else
|
|
34
|
+
FileUtils.cp(source, destination)
|
|
35
|
+
puts " [OK] File '#{item}' berhasil disalin."
|
|
36
|
+
end
|
|
37
|
+
elsif item == 'db'
|
|
38
|
+
FileUtils.mkdir_p(destination)
|
|
39
|
+
puts " [OK] Folder 'db' baru berhasil dibuat."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
puts "\nโ
Inisialisasi Selesai!"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_controller(name)
|
|
46
|
+
name = name.downcase
|
|
47
|
+
class_name = name.split('_').map(&:capitalize).join
|
|
48
|
+
file_path = "app/controllers/#{name}_controller.rb"
|
|
49
|
+
view_dir = "app/views/#{name}"
|
|
50
|
+
|
|
51
|
+
puts "๐ ๏ธ Generating controller: #{class_name}Controller"
|
|
52
|
+
|
|
53
|
+
content = <<~RUBY
|
|
54
|
+
class #{class_name}Controller < Eksa::Controller
|
|
55
|
+
def index
|
|
56
|
+
render "#{name}/index"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
RUBY
|
|
60
|
+
|
|
61
|
+
FileUtils.mkdir_p("app/controllers")
|
|
62
|
+
File.write(file_path, content)
|
|
63
|
+
puts " [OK] Created #{file_path}"
|
|
64
|
+
|
|
65
|
+
FileUtils.mkdir_p(view_dir)
|
|
66
|
+
index_view = File.join(view_dir, "index.html.erb")
|
|
67
|
+
File.write(index_view, "<h1>#{class_name}#index</h1>\n<p>Temukan saya di #{index_view}</p>")
|
|
68
|
+
puts " [OK] Created #{index_view}"
|
|
69
|
+
|
|
70
|
+
# Update config.ru with a new route
|
|
71
|
+
if File.exist?('config.ru')
|
|
72
|
+
config_content = File.read('config.ru')
|
|
73
|
+
route_line = %(app.add_route "/#{name}", #{class_name}Controller, :index)
|
|
74
|
+
|
|
75
|
+
unless config_content.include?(route_line)
|
|
76
|
+
if config_content.match?(/run app/)
|
|
77
|
+
new_content = config_content.gsub(/run app/, "#{route_line}\n\nrun app")
|
|
78
|
+
File.write('config.ru', new_content)
|
|
79
|
+
puts " [OK] Added route to config.ru"
|
|
80
|
+
else
|
|
81
|
+
File.open('config.ru', 'a') { |f| f.puts "\n#{route_line}" }
|
|
82
|
+
puts " [OK] Appended route to config.ru"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def generate_model(name)
|
|
89
|
+
name = name.downcase
|
|
90
|
+
class_name = name.split('_').map(&:capitalize).join
|
|
91
|
+
file_path = "app/models/#{name}.rb"
|
|
92
|
+
|
|
93
|
+
puts "๐ ๏ธ Generating model: #{class_name}"
|
|
94
|
+
|
|
95
|
+
content = <<~RUBY
|
|
96
|
+
class #{class_name} < Eksa::Model
|
|
97
|
+
def self.setup_schema
|
|
98
|
+
db.execute <<~SQL
|
|
99
|
+
CREATE TABLE IF NOT EXISTS #{name}s (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
102
|
+
)
|
|
103
|
+
SQL
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.all
|
|
107
|
+
db.execute("SELECT * FROM #{name}s ORDER BY id DESC")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
RUBY
|
|
111
|
+
|
|
112
|
+
FileUtils.mkdir_p("app/models")
|
|
113
|
+
File.write(file_path, content)
|
|
114
|
+
puts " [OK] Created #{file_path}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def start_server
|
|
118
|
+
if File.exist?('config.ru')
|
|
119
|
+
puts "๐ Memulai Eksa Framework Server..."
|
|
120
|
+
exec "rackup config.ru"
|
|
121
|
+
else
|
|
122
|
+
puts "โ Error: config.ru tidak ditemukan. Pastikan Anda berada di root project Eksa."
|
|
123
|
+
exit 1
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
command = ARGV.shift
|
|
128
|
+
|
|
129
|
+
case command
|
|
130
|
+
when 'init'
|
|
131
|
+
init_project
|
|
132
|
+
when 'run'
|
|
133
|
+
start_server
|
|
134
|
+
when 'g', 'generate'
|
|
135
|
+
subcommand = ARGV.shift
|
|
136
|
+
name = ARGV.shift
|
|
137
|
+
if subcommand == 'controller' && name
|
|
138
|
+
generate_controller(name)
|
|
139
|
+
elsif subcommand == 'model' && name
|
|
140
|
+
generate_model(name)
|
|
141
|
+
else
|
|
142
|
+
usage
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
usage
|
|
146
|
+
end
|
data/lib/eksa/controller.rb
CHANGED
|
@@ -15,6 +15,18 @@ module Eksa
|
|
|
15
15
|
@request.params
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def stylesheet_tag(filename)
|
|
19
|
+
"<link rel='stylesheet' href='/css/#{filename}.css'>"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def javascript_tag(filename)
|
|
23
|
+
"<script src='/js/#{filename}.js'></script>"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def asset_path(path)
|
|
27
|
+
path.start_with?('/') ? path : "/#{path}"
|
|
28
|
+
end
|
|
29
|
+
|
|
18
30
|
def redirect_to(url, notice: nil)
|
|
19
31
|
@status = 302
|
|
20
32
|
@redirect_url = url
|
|
@@ -36,7 +48,11 @@ module Eksa
|
|
|
36
48
|
@content
|
|
37
49
|
end
|
|
38
50
|
else
|
|
39
|
-
"
|
|
51
|
+
"<div class='glass' style='padding: 2rem; border-radius: 1rem; color: #ff5555; background: rgba(255,0,0,0.1); backdrop-filter: blur(10px);'>
|
|
52
|
+
<h2 style='margin-top:0;'>โ ๏ธ View Error</h2>
|
|
53
|
+
<p>Template <strong>#{template_name}</strong> tidak ditemukan di:</p>
|
|
54
|
+
<code style='display:block; background:rgba(0,0,0,0.2); padding:0.5rem; border-radius:0.5rem;'>#{content_path}</code>
|
|
55
|
+
</div>"
|
|
40
56
|
end
|
|
41
57
|
end
|
|
42
58
|
end
|
data/lib/eksa/model.rb
CHANGED
|
@@ -3,23 +3,28 @@ require 'fileutils'
|
|
|
3
3
|
|
|
4
4
|
module Eksa
|
|
5
5
|
class Model
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :database_path
|
|
8
|
+
end
|
|
9
|
+
|
|
6
10
|
def self.db
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
path = database_path || default_db_path
|
|
12
|
+
db_dir = File.dirname(path)
|
|
9
13
|
FileUtils.mkdir_p(db_dir) unless Dir.exist?(db_dir)
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
@db ||= SQLite3::Database.new(path)
|
|
16
|
+
ensure_schema
|
|
12
17
|
@db
|
|
13
18
|
end
|
|
14
19
|
|
|
15
|
-
def self.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
def self.default_db_path
|
|
21
|
+
File.expand_path("../../db/eksa_app.db", __dir__)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.ensure_schema
|
|
25
|
+
return if @schema_initialized
|
|
26
|
+
@schema_initialized = true
|
|
27
|
+
setup_schema if respond_to?(:setup_schema)
|
|
23
28
|
end
|
|
24
29
|
end
|
|
25
30
|
end
|
data/lib/eksa/version.rb
CHANGED
data/lib/eksa.rb
CHANGED
|
@@ -5,15 +5,45 @@ require_relative 'eksa/model'
|
|
|
5
5
|
|
|
6
6
|
module Eksa
|
|
7
7
|
class Application
|
|
8
|
+
attr_reader :config, :middlewares
|
|
9
|
+
|
|
8
10
|
def initialize
|
|
9
11
|
@routes = {}
|
|
12
|
+
@middlewares = []
|
|
13
|
+
@config = {
|
|
14
|
+
db_path: File.expand_path("./db/eksa_app.db")
|
|
15
|
+
}
|
|
16
|
+
yield self if block_given?
|
|
17
|
+
configure_framework
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure_framework
|
|
21
|
+
Eksa::Model.database_path = @config[:db_path]
|
|
10
22
|
end
|
|
11
23
|
|
|
12
24
|
def add_route(path, controller_class, action)
|
|
13
25
|
@routes[path] = { controller: controller_class, action: action }
|
|
14
26
|
end
|
|
15
27
|
|
|
28
|
+
def use(middleware, *args, &block)
|
|
29
|
+
@middlewares << [middleware, args, block]
|
|
30
|
+
end
|
|
31
|
+
|
|
16
32
|
def call(env)
|
|
33
|
+
@app ||= build_app
|
|
34
|
+
@app.call(env)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_app
|
|
38
|
+
builder = Rack::Builder.new
|
|
39
|
+
@middlewares.each do |middleware, args, block|
|
|
40
|
+
builder.use(middleware, *args, &block)
|
|
41
|
+
end
|
|
42
|
+
builder.run(method(:core_call))
|
|
43
|
+
builder.to_app
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def core_call(env)
|
|
17
47
|
request = Rack::Request.new(env)
|
|
18
48
|
flash_message = request.cookies['eksa_flash']
|
|
19
49
|
route = @routes[request.path_info]
|
|
@@ -38,7 +68,46 @@ module Eksa
|
|
|
38
68
|
response.delete_cookie('eksa_flash') if flash_message
|
|
39
69
|
response.finish
|
|
40
70
|
else
|
|
41
|
-
|
|
71
|
+
html = <<~HTML
|
|
72
|
+
<!DOCTYPE html>
|
|
73
|
+
<html lang="id">
|
|
74
|
+
<head>
|
|
75
|
+
<meta charset="UTF-8">
|
|
76
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
77
|
+
<title>404 - Halaman Tidak Ditemukan</title>
|
|
78
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
79
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
|
|
80
|
+
<script src="https://unpkg.com/lucide@latest"></script>
|
|
81
|
+
<style>
|
|
82
|
+
body { background: radial-gradient(circle at top left, #1e1b4b 0%, #000000 100%); min-height: 100vh; color: white; font-family: sans-serif; display: flex; align-items: center; justify-content: center; overflow: hidden; margin: 0; }
|
|
83
|
+
.glass { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); }
|
|
84
|
+
.glow { position: absolute; width: 400px; height: 400px; background: radial-gradient(circle, rgba(79, 70, 229, 0.15) 0%, transparent 70%); z-index: -1; filter: blur(40px); }
|
|
85
|
+
</style>
|
|
86
|
+
</head>
|
|
87
|
+
<body>
|
|
88
|
+
<div class="glow" style="top: 0; left: 0; transform: translate(-50%, -50%);"></div>
|
|
89
|
+
<div class="glow" style="bottom: 0; right: 0; transform: translate(50%, 50%);"></div>
|
|
90
|
+
<div class="glass" style="max-width: 512px; width: 100%; padding: 48px; border-radius: 40px; text-align: center; animation: zoomIn 0.8s;">
|
|
91
|
+
<div style="margin-bottom: 32px; position: relative; display: inline-block;">
|
|
92
|
+
<div style="position: absolute; inset: 0; background: rgba(79, 70, 229, 0.2); filter: blur(3xl); border-radius: 9999px;"></div>
|
|
93
|
+
<div style="position: relative; background: rgba(79, 70, 229, 0.2); padding: 24px; border-radius: 24px; border: 1px solid rgba(99, 102, 241, 0.3);">
|
|
94
|
+
<i data-lucide="ghost" style="width: 64px; height: 64px; color: #a5b4fc;"></i>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<h1 style="font-size: 80px; font-weight: 900; margin: 0; letter-spacing: -0.05em; color: white; opacity: 0.9;">404</h1>
|
|
98
|
+
<h2 style="font-size: 24px; font-weight: 700; margin: 16px 0; color: rgba(255, 255, 255, 0.9);">Oops! Halaman Hilang.</h2>
|
|
99
|
+
<p style="color: rgba(255, 255, 255, 0.5); margin-bottom: 40px; line-height: 1.6;">Sepertinya halaman yang Anda cari tidak ada atau sudah berpindah alamat. Jangan khawatir, kita bisa kembali.</p>
|
|
100
|
+
<div style="display: flex; flex-direction: column; gap: 16px; justify-content: center;">
|
|
101
|
+
<a href="/" style="padding: 16px 32px; background: white; color: black; font-weight: 700; border-radius: 16px; text-decoration: none; display: flex; items-center: center; justify-content: center; gap: 8px;">
|
|
102
|
+
<i data-lucide="home" style="width: 20px; height: 20px;"></i> Kembali ke Beranda
|
|
103
|
+
</a>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<script>lucide.createIcons();</script>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
HTML
|
|
110
|
+
[404, { 'content-type' => 'text/html' }, [html]]
|
|
42
111
|
end
|
|
43
112
|
end
|
|
44
113
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Eksa::Application do
|
|
4
|
+
let(:app) do
|
|
5
|
+
Eksa::Application.new do |config|
|
|
6
|
+
config.config[:db_path] = ":memory:" # Use in-memory DB for tests
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "returns 404 for unknown routes" do
|
|
11
|
+
get '/unknown'
|
|
12
|
+
expect(last_response.status).to eq(404)
|
|
13
|
+
expect(last_response.body).to include("Halaman tidak ditemukan")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "can add and reach routes" do
|
|
17
|
+
# Mock controller for testing
|
|
18
|
+
stub_const("TestController", Class.new(Eksa::Controller) do
|
|
19
|
+
def index
|
|
20
|
+
"Hello Test"
|
|
21
|
+
end
|
|
22
|
+
end)
|
|
23
|
+
|
|
24
|
+
app.add_route "/test", TestController, :index
|
|
25
|
+
get '/test'
|
|
26
|
+
expect(last_response.status).to eq(200)
|
|
27
|
+
expect(last_response.body).to eq("Hello Test")
|
|
28
|
+
end
|
|
29
|
+
end
|
data/spec/model_spec.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
class RecurrentModel < Eksa::Model
|
|
4
|
+
def self.setup_schema
|
|
5
|
+
db.execute "CREATE TABLE IF NOT EXISTS tests (id INTEGER PRIMARY KEY)"
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe Eksa::Model do
|
|
10
|
+
it "does not cause infinite recursion when setup_schema calls db" do
|
|
11
|
+
expect { RecurrentModel.db }.not_to raise_error
|
|
12
|
+
end
|
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
require 'rspec'
|
|
3
|
+
|
|
4
|
+
# Load the framework
|
|
5
|
+
require_relative '../lib/eksa'
|
|
6
|
+
|
|
7
|
+
# Set environment to test
|
|
8
|
+
ENV['RACK_ENV'] = 'test'
|
|
9
|
+
|
|
10
|
+
RSpec.configure do |config|
|
|
11
|
+
config.include Rack::Test::Methods
|
|
12
|
+
|
|
13
|
+
config.expect_with :rspec do |expectations|
|
|
14
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.mock_with :rspec do |mocks|
|
|
18
|
+
mocks.verify_partial_doubles = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
22
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eksa-framework
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- IshikawaUta
|
|
@@ -70,11 +70,12 @@ description: Framework MVC ringan dengan tema modern, sistem routing, dan auto-d
|
|
|
70
70
|
email:
|
|
71
71
|
- komikers09@gmail.com
|
|
72
72
|
executables:
|
|
73
|
-
- eksa
|
|
73
|
+
- eksa
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
77
|
- Gemfile
|
|
78
|
+
- Gemfile.lock
|
|
78
79
|
- LICENSE
|
|
79
80
|
- README.md
|
|
80
81
|
- app/controllers/pages_controller.rb
|
|
@@ -87,9 +88,8 @@ files:
|
|
|
87
88
|
- app/views/kontak.html.erb
|
|
88
89
|
- app/views/layout.html.erb
|
|
89
90
|
- config.ru
|
|
90
|
-
- db/eksa_app.db
|
|
91
91
|
- db/setup.rb
|
|
92
|
-
- exe/eksa
|
|
92
|
+
- exe/eksa
|
|
93
93
|
- lib/eksa.rb
|
|
94
94
|
- lib/eksa/controller.rb
|
|
95
95
|
- lib/eksa/model.rb
|
|
@@ -97,10 +97,16 @@ files:
|
|
|
97
97
|
- public/css/style.css
|
|
98
98
|
- public/img/favicon.ico
|
|
99
99
|
- public/img/logo.png
|
|
100
|
+
- spec/application_spec.rb
|
|
101
|
+
- spec/model_spec.rb
|
|
102
|
+
- spec/spec_helper.rb
|
|
100
103
|
homepage: https://github.com/IshikawaUta/eksa-framework
|
|
101
104
|
licenses:
|
|
102
105
|
- MIT
|
|
103
|
-
metadata:
|
|
106
|
+
metadata:
|
|
107
|
+
homepage_uri: https://github.com/IshikawaUta/eksa-framework
|
|
108
|
+
source_code_uri: https://github.com/IshikawaUta/eksa-framework
|
|
109
|
+
changelog_uri: https://github.com/IshikawaUta/eksa-framework/releases
|
|
104
110
|
rdoc_options: []
|
|
105
111
|
require_paths:
|
|
106
112
|
- lib
|
data/db/eksa_app.db
DELETED
|
Binary file
|
data/exe/eksa-init
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
gem_root = File.expand_path("../..", __FILE__)
|
|
7
|
-
target_dir = Dir.pwd
|
|
8
|
-
|
|
9
|
-
puts "\nโจ Eksa Framework Generator โจ"
|
|
10
|
-
puts "-----------------------------"
|
|
11
|
-
puts "๐ Menginisialisasi project di: #{target_dir}"
|
|
12
|
-
|
|
13
|
-
items_to_init = ['app', 'lib', 'public', 'db', 'config.ru', 'Gemfile', 'README.md']
|
|
14
|
-
|
|
15
|
-
items_to_init.each do |item|
|
|
16
|
-
source = File.join(gem_root, item)
|
|
17
|
-
destination = File.join(target_dir, item)
|
|
18
|
-
|
|
19
|
-
if File.exist?(source)
|
|
20
|
-
if File.directory?(source)
|
|
21
|
-
FileUtils.cp_r(source, target_dir)
|
|
22
|
-
puts " [OK] Folder '#{item}' berhasil disalin."
|
|
23
|
-
else
|
|
24
|
-
FileUtils.cp(source, destination)
|
|
25
|
-
puts " [OK] File '#{item}' berhasil disalin."
|
|
26
|
-
end
|
|
27
|
-
else
|
|
28
|
-
if item == 'db'
|
|
29
|
-
FileUtils.mkdir_p(destination)
|
|
30
|
-
puts " [OK] Folder 'db' baru berhasil dibuat."
|
|
31
|
-
else
|
|
32
|
-
puts " [!] Warning: Sumber '#{item}' tidak ditemukan."
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
puts "\nโ
Inisialisasi Selesai!"
|
|
38
|
-
puts "-----------------------------"
|
|
39
|
-
puts "Instruksi selanjutnya:"
|
|
40
|
-
puts "1. Jalankan: bundle install"
|
|
41
|
-
puts "2. Jalankan server: rackup config.ru"
|
|
42
|
-
puts "3. Buka browser: http://localhost:9292"
|
|
43
|
-
puts "-----------------------------\n"
|