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.
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: []