cybertrain 0.1.0
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 +7 -0
- data/README.md +461 -0
- data/cybertrain/cli/new_app.rb +75 -0
- data/cybertrain/cli/scaffold.rb +227 -0
- data/cybertrain/cli/templates.rb +421 -0
- data/cybertrain/cli.rb +100 -0
- data/cybertrain/generator/inflector.rb +126 -0
- data/cybertrain/version.rb +8 -0
- data/exe/cybertrain +7 -0
- metadata +53 -0
data/cybertrain/cli.rb
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# The `cybertrain` command: exe/cybertrain under CRuby (the gem), or
|
|
2
|
+
# bin/cybertrain.rb built by spin (`spin install` from a checkout).
|
|
3
|
+
#
|
|
4
|
+
# cybertrain new NAME [--path DIR | --version V] [--skip-spin]
|
|
5
|
+
# cybertrain generate scaffold NAME field:type ... [parent:references]
|
|
6
|
+
# cybertrain version | help
|
|
7
|
+
require "cybertrain/version"
|
|
8
|
+
require "cybertrain/cli/templates"
|
|
9
|
+
require "cybertrain/cli/new_app"
|
|
10
|
+
require "cybertrain/cli/scaffold"
|
|
11
|
+
|
|
12
|
+
module Cybertrain
|
|
13
|
+
module CLI
|
|
14
|
+
USAGE = <<~TEXT
|
|
15
|
+
Usage:
|
|
16
|
+
cybertrain new NAME [--path DIR | --version V] [--skip-spin]
|
|
17
|
+
Create an application in NAME, then run `spin lock` and
|
|
18
|
+
`spin run gen` in it (skipped with --skip-spin). Its spin.toml
|
|
19
|
+
depends on this cybertrain's release by default:
|
|
20
|
+
git tag v#{Cybertrain::VERSION} of #{Cybertrain::REPOSITORY}
|
|
21
|
+
--path DIR: the framework checkout at DIR (relative to the
|
|
22
|
+
current directory; written as an absolute path).
|
|
23
|
+
--version V: the index version constraint V.
|
|
24
|
+
cybertrain generate scaffold NAME field:type ... [parent:references]
|
|
25
|
+
Add a resource to the application in the current directory.
|
|
26
|
+
Types: #{Field::TYPES.join(", ")} (default string).
|
|
27
|
+
cybertrain version
|
|
28
|
+
cybertrain help
|
|
29
|
+
TEXT
|
|
30
|
+
|
|
31
|
+
# Returns the process exit code.
|
|
32
|
+
def self.run(argv)
|
|
33
|
+
command = argv.empty? ? "" : argv[0]
|
|
34
|
+
case command
|
|
35
|
+
when "new" then run_new(argv)
|
|
36
|
+
when "generate", "g" then run_generate(argv)
|
|
37
|
+
when "version", "--version", "-v"
|
|
38
|
+
puts "cybertrain #{Cybertrain::VERSION}"
|
|
39
|
+
0
|
|
40
|
+
when "help", "--help", "-h"
|
|
41
|
+
puts USAGE
|
|
42
|
+
0
|
|
43
|
+
else
|
|
44
|
+
puts "Unknown command '#{command}'" unless command == ""
|
|
45
|
+
puts USAGE
|
|
46
|
+
1
|
|
47
|
+
end
|
|
48
|
+
rescue InvalidArgument => e
|
|
49
|
+
puts "error: #{e.message}"
|
|
50
|
+
1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.run_new(argv)
|
|
54
|
+
raise InvalidArgument, "usage: cybertrain new NAME [--path DIR | --version V] [--skip-spin]" if argv.size < 2
|
|
55
|
+
|
|
56
|
+
dir = argv[1]
|
|
57
|
+
raise InvalidArgument, "'#{File.basename(dir)}' is not a valid app name (lowercase letters, digits and _)" unless Templates.identifier?(File.basename(dir))
|
|
58
|
+
raise InvalidArgument, "#{dir} already exists" if File.exist?(dir)
|
|
59
|
+
|
|
60
|
+
NewApp.create(dir, framework_dep(argv))
|
|
61
|
+
return 0 if argv.include?("--skip-spin")
|
|
62
|
+
|
|
63
|
+
NewApp.bootstrap(dir) ? 0 : 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The spin.toml value of the `cybertrain =` dependency. By default the
|
|
67
|
+
# release tag matching this CLI, so the templates it just wrote and the
|
|
68
|
+
# framework the app builds against are the same version.
|
|
69
|
+
# A relative --path is expanded against the current directory: spin
|
|
70
|
+
# resolves `path =` from the new app's directory, not from where the
|
|
71
|
+
# command ran.
|
|
72
|
+
def self.framework_dep(argv)
|
|
73
|
+
path = option(argv, "--path")
|
|
74
|
+
version = option(argv, "--version")
|
|
75
|
+
raise InvalidArgument, "pass either --path or --version, not both" unless path == "" || version == ""
|
|
76
|
+
return "\"#{version}\"" unless version == ""
|
|
77
|
+
return "{ path = \"#{File.expand_path(path, Dir.pwd)}\" }" unless path == ""
|
|
78
|
+
|
|
79
|
+
"{ git = \"#{Cybertrain::REPOSITORY}\", ref = \"v#{Cybertrain::VERSION}\" }"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The value after `--flag`, or "" when the flag is absent.
|
|
83
|
+
def self.option(argv, flag)
|
|
84
|
+
i = argv.index(flag)
|
|
85
|
+
return "" if i.nil?
|
|
86
|
+
raise InvalidArgument, "#{flag} needs a value" if i + 1 >= argv.size
|
|
87
|
+
|
|
88
|
+
argv[i + 1]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.run_generate(argv)
|
|
92
|
+
kind = argv.size > 1 ? argv[1] : ""
|
|
93
|
+
raise InvalidArgument, "only `generate scaffold` is supported" unless kind == "scaffold"
|
|
94
|
+
raise InvalidArgument, "usage: cybertrain generate scaffold NAME field:type ..." if argv.size < 3
|
|
95
|
+
|
|
96
|
+
Scaffold.generate(".", argv[2], argv[3, argv.size - 3])
|
|
97
|
+
0
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module Cybertrain
|
|
2
|
+
# The handful of Rails inflections the generators need. Singular/plural
|
|
3
|
+
# forms come from a small irregulars table plus English suffix rules;
|
|
4
|
+
# a word that is in neither simply gets the rules ("octopus" -> "octopuses").
|
|
5
|
+
module Inflector
|
|
6
|
+
IRREGULARS = {
|
|
7
|
+
"person" => "people", "man" => "men", "woman" => "women",
|
|
8
|
+
"child" => "children", "mouse" => "mice", "ox" => "oxen"
|
|
9
|
+
}
|
|
10
|
+
UNCOUNTABLE = %w[equipment information rice money species series fish sheep news jeans]
|
|
11
|
+
# Singulars ending in "s" whose plural adds "es" (Rails' alias/status/bus rules).
|
|
12
|
+
ES_WORDS = %w[status alias bus]
|
|
13
|
+
|
|
14
|
+
# "posts_controller" -> "PostsController"; "admin/posts" -> "Admin::Posts".
|
|
15
|
+
def self.camelize(str)
|
|
16
|
+
out = +""
|
|
17
|
+
upper = true
|
|
18
|
+
str.each_char do |ch|
|
|
19
|
+
if ch == "_"
|
|
20
|
+
upper = true
|
|
21
|
+
elsif ch == "/"
|
|
22
|
+
out << "::"
|
|
23
|
+
upper = true
|
|
24
|
+
elsif upper
|
|
25
|
+
out << ch.upcase
|
|
26
|
+
upper = false
|
|
27
|
+
else
|
|
28
|
+
out << ch
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
out
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# "PostsController" -> "posts_controller"; "HTMLParser" -> "html_parser".
|
|
35
|
+
def self.underscore(str)
|
|
36
|
+
# Two steps: in a threaded program a collection inside `chars` freed the
|
|
37
|
+
# unnamed gsub result it was splitting ("Post" came back as "").
|
|
38
|
+
path = str.gsub("::", "/")
|
|
39
|
+
chars = path.chars
|
|
40
|
+
out = +""
|
|
41
|
+
chars.each_with_index do |ch, i|
|
|
42
|
+
if upper?(ch)
|
|
43
|
+
prev = i > 0 ? chars[i - 1] : ""
|
|
44
|
+
nxt = i + 1 < chars.size ? chars[i + 1] : ""
|
|
45
|
+
if prev != "" && prev != "/" && (lower_or_digit?(prev) || (upper?(prev) && lower_or_digit?(nxt)))
|
|
46
|
+
out << "_"
|
|
47
|
+
end
|
|
48
|
+
out << ch.downcase
|
|
49
|
+
else
|
|
50
|
+
out << ch
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
out
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# "posts" -> "post"; only the last "_" word is inflected ("blog_posts").
|
|
57
|
+
def self.singularize(str)
|
|
58
|
+
parts = split_last_word(str)
|
|
59
|
+
parts[0] + singular_word(parts[1])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# "post" -> "posts"; only the last "_" word is inflected.
|
|
63
|
+
def self.pluralize(str)
|
|
64
|
+
parts = split_last_word(str)
|
|
65
|
+
parts[0] + plural_word(parts[1])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.singular_word(word)
|
|
69
|
+
return word if UNCOUNTABLE.include?(word)
|
|
70
|
+
|
|
71
|
+
IRREGULARS.each { |singular, plural| return singular if word == plural }
|
|
72
|
+
return word if IRREGULARS.key?(word)
|
|
73
|
+
|
|
74
|
+
if word.end_with?("ies") && word.size > 3 && !vowel?(word[word.size - 4])
|
|
75
|
+
word[0, word.size - 3] + "y"
|
|
76
|
+
elsif ES_WORDS.include?(word[0, word.size - 2]) || word.end_with?("sses") ||
|
|
77
|
+
word.end_with?("xes") || word.end_with?("ches") || word.end_with?("shes")
|
|
78
|
+
word[0, word.size - 2]
|
|
79
|
+
elsif word.end_with?("ss") || ES_WORDS.include?(word)
|
|
80
|
+
word
|
|
81
|
+
elsif word.end_with?("s")
|
|
82
|
+
word[0, word.size - 1]
|
|
83
|
+
else
|
|
84
|
+
word
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.plural_word(word)
|
|
89
|
+
return word if UNCOUNTABLE.include?(word)
|
|
90
|
+
|
|
91
|
+
plural = IRREGULARS[word]
|
|
92
|
+
return plural unless plural.nil?
|
|
93
|
+
|
|
94
|
+
if word.end_with?("y") && word.size > 1 && !vowel?(word[word.size - 2])
|
|
95
|
+
word[0, word.size - 1] + "ies"
|
|
96
|
+
elsif ES_WORDS.include?(word) || word.end_with?("ss") || word.end_with?("x") ||
|
|
97
|
+
word.end_with?("ch") || word.end_with?("sh")
|
|
98
|
+
word + "es"
|
|
99
|
+
elsif word.end_with?("s")
|
|
100
|
+
word
|
|
101
|
+
else
|
|
102
|
+
word + "s"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# "sales_people" -> ["sales_", "people"].
|
|
107
|
+
def self.split_last_word(str)
|
|
108
|
+
i = str.rindex("_")
|
|
109
|
+
return ["", str] if i.nil?
|
|
110
|
+
|
|
111
|
+
[str[0, i + 1], str[i + 1, str.size - i - 1]]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.vowel?(ch)
|
|
115
|
+
ch == "a" || ch == "e" || ch == "i" || ch == "o" || ch == "u"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.upper?(ch)
|
|
119
|
+
ch >= "A" && ch <= "Z"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.lower_or_digit?(ch)
|
|
123
|
+
(ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module Cybertrain
|
|
2
|
+
VERSION = "0.1.0"
|
|
3
|
+
|
|
4
|
+
# Where `cybertrain new` points an app's spin.toml by default: the git tag
|
|
5
|
+
# "v#{VERSION}" of this repository. Kept here because the gem (CRuby) and
|
|
6
|
+
# the spin-built CLI read the same file.
|
|
7
|
+
REPOSITORY = "https://github.com/saeki-mototsune/cybertrain"
|
|
8
|
+
end
|
data/exe/cybertrain
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# The `cybertrain` command as installed by `gem install cybertrain`: the CLI
|
|
3
|
+
# sources are plain Ruby that also run under CRuby (bin/cybertrain.rb is the
|
|
4
|
+
# same entry point, compiled by spin).
|
|
5
|
+
require "cybertrain/cli"
|
|
6
|
+
|
|
7
|
+
exit(Cybertrain::CLI.run(ARGV))
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cybertrain
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Saeki Mototsune
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |
|
|
13
|
+
cybertrain is a Rails-shaped web framework written for Spinel, which
|
|
14
|
+
compiles an application into a native binary. This gem installs the
|
|
15
|
+
`cybertrain` command (`cybertrain new`, `cybertrain generate scaffold`);
|
|
16
|
+
building and running an application needs Spinel's `spin` on PATH.
|
|
17
|
+
executables:
|
|
18
|
+
- cybertrain
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- README.md
|
|
23
|
+
- cybertrain/cli.rb
|
|
24
|
+
- cybertrain/cli/new_app.rb
|
|
25
|
+
- cybertrain/cli/scaffold.rb
|
|
26
|
+
- cybertrain/cli/templates.rb
|
|
27
|
+
- cybertrain/generator/inflector.rb
|
|
28
|
+
- cybertrain/version.rb
|
|
29
|
+
- exe/cybertrain
|
|
30
|
+
homepage: https://github.com/saeki-mototsune/cybertrain
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata:
|
|
34
|
+
source_code_uri: https://github.com/saeki-mototsune/cybertrain
|
|
35
|
+
rubygems_mfa_required: 'true'
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- "."
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '3.2'
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 4.0.8
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: 'The cybertrain CLI: scaffold Rails-shaped apps for the Spinel AOT Ruby compiler'
|
|
53
|
+
test_files: []
|