cybertrain 0.1.1 → 0.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/README.md +233 -67
- data/cybertrain/app_name.rb +36 -0
- data/cybertrain/cli/build.rb +93 -16
- data/cybertrain/cli/new_app.rb +22 -12
- data/cybertrain/cli/scaffold.rb +78 -11
- data/cybertrain/cli/templates.rb +16 -20
- data/cybertrain/cli/toolchain.rb +787 -0
- data/cybertrain/cli.rb +114 -25
- data/cybertrain/generator/inflector.rb +154 -88
- data/cybertrain/ident.rb +171 -0
- data/cybertrain/version.rb +5 -1
- metadata +8 -3
data/cybertrain/ident.rb
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Cybertrain::Ident -- the one place that says what a column (or scaffold
|
|
2
|
+
# field) may be called. Used by the generator (ModelsEmitter), the ORM
|
|
3
|
+
# (Relation#order) and the CLI (`cybertrain generate scaffold`), so a name
|
|
4
|
+
# the scaffold accepts is a name the generator and the models accept, and
|
|
5
|
+
# the failure comes from the command that would write the files.
|
|
6
|
+
#
|
|
7
|
+
# Plain Ruby with no requires: the CLI gem ships this file and runs it under
|
|
8
|
+
# CRuby, the framework compiles it under Spinel (no Regexp, NOTES rule 1).
|
|
9
|
+
module Cybertrain
|
|
10
|
+
module Ident
|
|
11
|
+
# Column names whose plain reader the generated class cannot host: it
|
|
12
|
+
# would shadow a method the generated class or the framework calls on the
|
|
13
|
+
# record. The generator renames them, so an existing schema keeps
|
|
14
|
+
# generating: the column reads as `<column>_column`
|
|
15
|
+
# (ModelsEmitter.reader_name). The scaffold refuses them: its views and
|
|
16
|
+
# controller call the reader by the field's name, which a renamed reader
|
|
17
|
+
# would break. `id` is deliberately
|
|
18
|
+
# absent: the primary key is Model#id. Names that end in `?` or `!`
|
|
19
|
+
# cannot be columns at all (column? rejects them), so `valid?`,
|
|
20
|
+
# `persisted?`, `is_a?` need no entry; keywords (`class`) live in
|
|
21
|
+
# RUBY_KEYWORDS only, and the generator renames those the same way
|
|
22
|
+
# (`class_column`). Class methods (`table_name`, `column_names`,
|
|
23
|
+
# `from_row`) need no entry either: the framework calls them on the
|
|
24
|
+
# class (`self.class.table_name`, `Post.from_row`), which a column
|
|
25
|
+
# reader on the instance does not shadow, so a schema with a
|
|
26
|
+
# `table_name` column keeps generating. script/check-reserved-names
|
|
27
|
+
# keeps the Model and Object groups here and in SHADOWING_COLUMN_NAMES
|
|
28
|
+
# in step with the code (it skips `def self.` methods).
|
|
29
|
+
RESERVED_COLUMN_NAMES = [
|
|
30
|
+
# Cybertrain::Model: its methods and the ivars behind them (`errors`,
|
|
31
|
+
# `persisted` -> @errors, @persisted)
|
|
32
|
+
"errors", "persisted", "attributes", "save", "update", "destroy",
|
|
33
|
+
"reload", "model_name", "to_json", "as_json", "to_param", "to_row",
|
|
34
|
+
"load_row", "set_id", "run_callbacks", "insert_row", "update_row",
|
|
35
|
+
"read_attribute", "write_attribute", "assign_attributes",
|
|
36
|
+
"read_association", "call_view_method", "initialize",
|
|
37
|
+
# Object methods Ruby or the framework calls on a record without being
|
|
38
|
+
# asked: string interpolation and templates (to_s), error messages
|
|
39
|
+
# (inspect), Hash keys (hash)
|
|
40
|
+
"hash", "inspect", "to_s",
|
|
41
|
+
# Implicit-conversion and dispatch hooks Ruby calls on its own (none is
|
|
42
|
+
# defined on Object, so no reflection finds them): an arity-0 reader
|
|
43
|
+
# returning a String breaks `puts post` / `[post].flatten` / a splat
|
|
44
|
+
# (to_ary), string coercion (to_str), keyword splats (to_hash), `&post`
|
|
45
|
+
# (to_proc), Integer coercion (to_int), and turns every NoMethodError
|
|
46
|
+
# into an ArgumentError (method_missing). `respond_to_missing?` ends in
|
|
47
|
+
# `?` and needs no entry.
|
|
48
|
+
"to_ary", "to_str", "to_hash", "to_proc", "to_int", "to_a", "to_h",
|
|
49
|
+
"to_sym", "to_io", "to_path", "to_regexp", "coerce", "method_missing",
|
|
50
|
+
# Hooks Ruby itself calls WITH an argument, which it finds whether they
|
|
51
|
+
# are public or private (so Object.instance_methods never lists the
|
|
52
|
+
# private ones): `record.dup` / `clone` call initialize_copy(orig)
|
|
53
|
+
# (initialize_dup / initialize_clone on the way), and defining, removing
|
|
54
|
+
# or undefining a singleton method (`def record.x`) calls
|
|
55
|
+
# singleton_method_added / _removed / _undefined. A zero-arity column
|
|
56
|
+
# reader in their place raises ArgumentError at that call. These are
|
|
57
|
+
# reserved, not merely shadowing: the problem is Ruby's call, not a
|
|
58
|
+
# method the caller could have wanted. script/check-reserved-names
|
|
59
|
+
# fails when one of Ruby's private hooks is missing here.
|
|
60
|
+
"initialize_copy", "initialize_dup", "initialize_clone",
|
|
61
|
+
"singleton_method_added", "singleton_method_removed",
|
|
62
|
+
"singleton_method_undefined",
|
|
63
|
+
# The one Kernel method the model calls on implicit self (Model#save!
|
|
64
|
+
# and #reload would dispatch `raise RecordInvalid, ...` to the
|
|
65
|
+
# zero-arity reader), and its alias. No other private Kernel method is
|
|
66
|
+
# reserved: `open`, `format`, `load`, `print`, `select`, `test` are
|
|
67
|
+
# ordinary column names and nothing in the generated class calls them.
|
|
68
|
+
"raise", "fail"
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
# The rest of Object's public instance methods: a column so named shadows
|
|
72
|
+
# them on its model (`record.display`, `record.tap` are the column), but
|
|
73
|
+
# neither the generated class nor the framework calls any of them on a
|
|
74
|
+
# record, so the generator accepts the column and writes a note into the
|
|
75
|
+
# generated file instead of refusing an existing schema. The scaffold
|
|
76
|
+
# accepts them the same way, with a note on stdout (Scaffold.shadow_notes):
|
|
77
|
+
# `payment.method` is the field, which its views and controller call.
|
|
78
|
+
SHADOWING_COLUMN_NAMES = [
|
|
79
|
+
"object_id", "__id__", "send", "__send__", "public_send", "freeze",
|
|
80
|
+
"display", "method", "methods", "public_method", "public_methods",
|
|
81
|
+
"private_methods", "protected_methods", "singleton_class",
|
|
82
|
+
"singleton_method", "singleton_methods", "define_singleton_method",
|
|
83
|
+
"remove_instance_variable", "instance_variable_get",
|
|
84
|
+
"instance_variable_set", "instance_variables", "instance_eval",
|
|
85
|
+
"instance_exec", "dup", "clone", "tap", "yield_self", "itself",
|
|
86
|
+
"extend", "enum_for", "to_enum"
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
# Ruby keywords a column could be spelled like: `@end` / `def class` are
|
|
90
|
+
# not shapes worth supporting. The generator reads such a column as
|
|
91
|
+
# `<column>_column` (ModelsEmitter.reader_name), like a reserved name;
|
|
92
|
+
# the scaffold refuses them (its views call the reader by name). The
|
|
93
|
+
# uppercase ones are reachable since column? accepts capitals. `defined`
|
|
94
|
+
# is not here: the keyword is `defined?`, which column? rejects, and
|
|
95
|
+
# `defined` is an ordinary method name (`attr_accessor :defined` works).
|
|
96
|
+
RUBY_KEYWORDS = [
|
|
97
|
+
"alias", "and", "begin", "break", "case", "class", "def", "do",
|
|
98
|
+
"else", "elsif", "end", "ensure", "false", "for", "if", "in",
|
|
99
|
+
"module", "next", "nil", "not", "or", "redo", "rescue", "retry",
|
|
100
|
+
"return", "self", "super", "then", "true", "undef", "unless", "until",
|
|
101
|
+
"when", "while", "yield",
|
|
102
|
+
"BEGIN", "END", "__ENCODING__", "__FILE__", "__LINE__"
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# /\A[A-Za-z_][A-Za-z0-9_]*\z/ spelled out: what Ruby needs of a method
|
|
106
|
+
# name, so a schema with camelCase or mixed-case columns (`createdAt`,
|
|
107
|
+
# `userId`, a legacy SQLite table) generates under its own name, not a
|
|
108
|
+
# renamed one; `attr_accessor :createdAt` and `attr_accessor :Title` both
|
|
109
|
+
# compile and dispatch under Spinel (probed on 2026.09.12). The
|
|
110
|
+
# scaffold, which chooses new names, keeps the stricter snake_case rule
|
|
111
|
+
# (CLI::Templates.identifier?). A flag rather than a `return` inside
|
|
112
|
+
# the block, like Templates.identifier?: a `return` from an each_char
|
|
113
|
+
# block does leave the method under Spinel (probed), but the flag needs
|
|
114
|
+
# no such guarantee.
|
|
115
|
+
def self.column?(name)
|
|
116
|
+
return false if name.empty?
|
|
117
|
+
|
|
118
|
+
ok = true
|
|
119
|
+
first = true
|
|
120
|
+
name.each_char do |ch|
|
|
121
|
+
letter = (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z")
|
|
122
|
+
digit = ch >= "0" && ch <= "9"
|
|
123
|
+
ok = false unless letter || ch == "_" || (digit && !first)
|
|
124
|
+
first = false
|
|
125
|
+
end
|
|
126
|
+
ok
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# The snake_case subset of column?: lowercase only, and a leading `_`
|
|
130
|
+
# only when allowed. CLI::Templates.identifier? (app, resource and field
|
|
131
|
+
# names: letter-first) and Template::Lexer.identifier? (template locals
|
|
132
|
+
# and block parameters: `_x` allowed) are both this predicate, so the two
|
|
133
|
+
# cannot drift. A positional default rather than a keyword argument: this
|
|
134
|
+
# file compiles under Spinel and keeps to the shapes it already uses.
|
|
135
|
+
def self.snake_case?(word, leading_underscore = true)
|
|
136
|
+
column?(word) && word == word.downcase && (leading_underscore || !word.start_with?("_"))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.keyword?(name)
|
|
140
|
+
RUBY_KEYWORDS.include?(name)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.reserved_column?(name)
|
|
144
|
+
RESERVED_COLUMN_NAMES.include?(name)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.shadowing_column?(name)
|
|
148
|
+
SHADOWING_COLUMN_NAMES.include?(name)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The one definition of "a name the scaffold must not invent": "" when
|
|
152
|
+
# `name` may be chosen, otherwise why not -- "keyword" (RUBY_KEYWORDS) or
|
|
153
|
+
# "reserved" (RESERVED_COLUMN_NAMES): the two kinds the generator RENAMES,
|
|
154
|
+
# which the scaffold cannot accept because its views and controller call
|
|
155
|
+
# the reader by the field's name. A SHADOWING_COLUMN_NAMES entry is not
|
|
156
|
+
# refused: the generator keeps its name with a note, and so does the
|
|
157
|
+
# scaffold (shadowing_column?). The scaffold asks here at every place it
|
|
158
|
+
# takes a name from the user (field column, references reader, resource
|
|
159
|
+
# singular) and raises the message for the reason, so a new list of names
|
|
160
|
+
# is added to this method once, not to each call site. A String, not nil
|
|
161
|
+
# or a Symbol, so the return type is the same on every path (NOTES rules
|
|
162
|
+
# 10/34); a keyword that is also listed in a column array reports
|
|
163
|
+
# "keyword" first.
|
|
164
|
+
def self.unusable_reason(name)
|
|
165
|
+
return "keyword" if keyword?(name)
|
|
166
|
+
return "reserved" if reserved_column?(name)
|
|
167
|
+
|
|
168
|
+
""
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
data/cybertrain/version.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
module Cybertrain
|
|
2
|
-
VERSION = "0.
|
|
2
|
+
VERSION = "0.2.1"
|
|
3
3
|
|
|
4
4
|
# Where `cybertrain new` points an app's spin.toml by default: the git tag
|
|
5
5
|
# "v#{VERSION}" of this repository. Kept here because the gem (CRuby) and
|
|
6
6
|
# the spin-built CLI read the same file.
|
|
7
7
|
REPOSITORY = "https://github.com/saeki-mototsune/cybertrain"
|
|
8
|
+
|
|
9
|
+
# The Spinel release cybertrain is written and tested against: the tag
|
|
10
|
+
# `cybertrain setup` builds and CI's SPINEL_TAG.
|
|
11
|
+
SPINEL_TAG = "2026.09.12"
|
|
8
12
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cybertrain
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Saeki Mototsune
|
|
@@ -12,20 +12,25 @@ dependencies: []
|
|
|
12
12
|
description: |
|
|
13
13
|
cybertrain is a Rails-shaped web framework written for Spinel, which
|
|
14
14
|
compiles an application into a native binary. This gem installs the
|
|
15
|
-
`cybertrain` command (`cybertrain new`, `
|
|
16
|
-
|
|
15
|
+
`cybertrain` command (`cybertrain new`, `generate scaffold`,
|
|
16
|
+
`db migrate`, `server`, `build`). The first command that needs Spinel
|
|
17
|
+
builds the pinned release into ~/.cybertrain (a C compiler, make, git,
|
|
18
|
+
curl and the SQLite headers are required); `cybertrain doctor` checks.
|
|
17
19
|
executables:
|
|
18
20
|
- cybertrain
|
|
19
21
|
extensions: []
|
|
20
22
|
extra_rdoc_files: []
|
|
21
23
|
files:
|
|
22
24
|
- README.md
|
|
25
|
+
- cybertrain/app_name.rb
|
|
23
26
|
- cybertrain/cli.rb
|
|
24
27
|
- cybertrain/cli/build.rb
|
|
25
28
|
- cybertrain/cli/new_app.rb
|
|
26
29
|
- cybertrain/cli/scaffold.rb
|
|
27
30
|
- cybertrain/cli/templates.rb
|
|
31
|
+
- cybertrain/cli/toolchain.rb
|
|
28
32
|
- cybertrain/generator/inflector.rb
|
|
33
|
+
- cybertrain/ident.rb
|
|
29
34
|
- cybertrain/version.rb
|
|
30
35
|
- exe/cybertrain
|
|
31
36
|
homepage: https://github.com/saeki-mototsune/cybertrain
|