Finalize changes to `latest_corpus.rb`

pull/1125/head
Rick Carlino 2019-02-25 13:46:06 -06:00
parent cb36bc0727
commit 8b79fdd866
4 changed files with 142 additions and 293 deletions

View File

@ -1,153 +1,143 @@
class CorpusEmitter
PIPE = "\n | "
WARNING_HEADER = \
"""
// THIS INTERFACE WAS AUTO GENERATED ON #{Date.today}
// DO NOT EDIT THIS FILE.
// IT WILL BE OVERWRITTEN ON EVERY CELERYSCRIPT UPGRADE.
class CSArg
TRANSLATIONS = {"integer" => "number",
"string" => "string",
"float" => "number",
"boolean" => "boolean" }
attr_reader :name, :allowed_values
def initialize(name:, allowed_values:)
@name, @allowed_values = name, allowed_values
end
def camelize
name.camelize
end
def values
allowed_values
.map { |v| TRANSLATIONS[v] || v.camelize }
.uniq
.sort
.join("\n | ")
end
def to_ts
"\n #{name}: #{values};"
end
"""
HASH = Sequence::Corpus.as_json({})
OUTPUT = [WARNING_HEADER]
FILE_PATH = "latest_corpus.ts"
VALUES = HASH.fetch(:values)
VALUE_PREFIX = "CS"
VALUES_TPL = "export type %{name} = %{type};\n"
VALUES_OVERRIDE = HashWithIndifferentAccess.new(float: "number", integer: "number")
# There are some rule exceptions when generating the Typescript corpus.
FUNNY_NAMES = { "Example" => "CSExample" }
ENUMS = HASH.fetch(:enums)
ENUM_TPL = "export type %{name} = %{type};\n"
ARGS = HASH
.fetch(:args)
.reduce(HashWithIndifferentAccess.new) do |acc, arg|
acc[arg.fetch("name").to_s] = arg
acc
end
class CSNode
UNDEFINED = "undefined"
attr_reader :name, :allowed_args, :allowed_body_types
def initialize(name:, allowed_args:, allowed_body_types: [], tags:)
@name,
@allowed_args,
@allowed_body_types = name, allowed_args, allowed_body_types
end
def camelize
name.camelize
end
def arg_names
allowed_args
.map{ |x| ARGS[x] }
.each { |x| raise "NON-EXISTENT ARG TYPE" unless x }
.map(&:to_ts)
.join("")
end
def items_joined_by_pipe
allowed_body_types.map(&:camelize).join(PIPE)
end
def body_names
b = items_joined_by_pipe
(b.length > 0) ? "(#{b})[] | undefined" : UNDEFINED
end
def has_body?
body_names != UNDEFINED
end
def body_type
"export type #{camelize}BodyItem = #{items_joined_by_pipe};" if has_body?
end
def body_attr
"body?: #{ has_body? ? (camelize + "BodyItem[] | undefined") : UNDEFINED };"
end
def to_ts
<<~NODE_EXPORTS
#{body_type}
export interface #{camelize} {
kind: #{name.inspect};
args: {#{arg_names}
};
comment?: string | undefined;
#{body_attr}
}
NODE_EXPORTS
end
end
HASH = JSON.load(open("http://#{ENV.fetch("API_HOST")}:3000/api/corpus")).deep_symbolize_keys
ARGS = {}
HASH[:args]
.map { |x| CSArg.new(x) }
.each { |x| ARGS[x.name] = x }
NODES = HASH[:nodes].map { |x| CSNode.new(x) }
def const(key, val)
"export const #{key} = #{val};"
end
def enum_type(key, val, inspect = true)
"export type #{key} = #{(inspect ? val.map(&:inspect) : val).join(PIPE)};"
end
def self.generate
self.new.generate
end
def overwrite_warning_comment
<<~COMMENT.strip
// THIS INTERFACE WAS AUTO GENERATED ON #{Date.today}
// DO NOT EDIT THIS FILE.
// IT WILL BE OVERWRITTEN ON EVERY CELERYSCRIPT UPGRADE.
COMMENT
end
def generate
result = NODES.map(&:to_ts).map(&:strip)
result.unshift(overwrite_warning_comment)
result.push(enum_type :CeleryNode, NODES.map(&:name).map(&:camelize), false)
result.push(const(:LATEST_VERSION, Sequence::LATEST_VERSION))
result.push(const :DIGITAL, CeleryScriptSettingsBag::DIGITAL)
result.push(const :ANALOG, CeleryScriptSettingsBag::ANALOG)
result.push(enum_type :ALLOWED_PIN_MODES,
CeleryScriptSettingsBag::ALLOWED_PIN_MODES)
result.push(enum_type :ALLOWED_MESSAGE_TYPES,
CeleryScriptSettingsBag::ALLOWED_MESSAGE_TYPES)
result.push(enum_type :ALLOWED_CHANNEL_NAMES,
CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES)
result.push(enum_type :ALLOWED_OPS,
CeleryScriptSettingsBag::ALLOWED_OPS)
result.push(enum_type :ALLOWED_PACKAGES,
CeleryScriptSettingsBag::ALLOWED_PACKAGES)
result.push(enum_type :ALLOWED_AXIS, CeleryScriptSettingsBag::ALLOWED_AXIS)
result.push(enum_type :Color, Sequence::COLORS)
result.push(enum_type :LegalArgString, HASH[:args].map{ |x| x[:name] }.sort.uniq)
result.push(enum_type :LegalKindString, HASH[:nodes].map{ |x| x[:name] }.sort.uniq)
result.push(enum_type :LegalSequenceKind, CeleryScriptSettingsBag::ALLOWED_RPC_NODES.sort)
result.push(enum_type :DataChangeType, CeleryScriptSettingsBag::ALLOWED_CHAGES)
result.push(enum_type :PointType, CeleryScriptSettingsBag::ALLOWED_POINTER_TYPE)
result.push(enum_type :AllowedPinTypes, CeleryScriptSettingsBag::ALLOWED_PIN_TYPES)
result.push(enum_type :PlantStage, CeleryScriptSettingsBag::PLANT_STAGES)
result.push(enum_type :AllowedGroupTypes, CeleryScriptSettingsBag::ALLOWED_EVERY_POINT_TYPE)
File.open("latest_corpus.ts", "w") do |f|
f.write(result.join("\n\n").concat("\n"))
end
NODES = HASH.fetch(:nodes)
NODE_START = [ "export type %{camel_case}BodyItem = %{body_types};",
"/** %{docs} %{tag_docs} */",
"export interface %{camel_case} {",
" comment?: string | undefined;",
' kind: "%{snake_case}";',
" args: {", ].join("\n")
MIDDLE_CENTER = " %{arg_name}: %{arg_values};"
BOTTOM_END = [ " }",
" body?: %{camel_case}BodyItem[] | undefined;",
"}\n", ].join("\n")
CONSTANT_DECLR_HACK = {
LATEST_VERSION: Sequence::LATEST_VERSION,
DIGITAL: CeleryScriptSettingsBag::DIGITAL,
ANALOG: CeleryScriptSettingsBag::ANALOG,
}
CONSTANT_DECLR_HACK_TPL = "export const %{name} = %{value};\n"
PUBLIC_NODES = [] # Filled at runtime
def emit_constants()
CONSTANT_DECLR_HACK.map do |(name, value)|
konst = CONSTANT_DECLR_HACK_TPL % { name: name, value: value }
add_to_output(konst)
end
end
CorpusEmitter.generate
def add_to_output(string)
OUTPUT.push(string)
end
def save!
File.open(FILE_PATH, "w") { |f| f.write(OUTPUT.join("")) }
puts "Saved to #{FILE_PATH}"
end
def name_of(thing)
thing.fetch("name").to_s
end
def emit_values
output = VALUES.map do |val|
real_name = name_of(val)
capitalized = real_name.capitalize
celerized = VALUE_PREFIX + capitalized
FUNNY_NAMES[capitalized] = celerized
type = VALUES_OVERRIDE.fetch(real_name, real_name)
VALUES_TPL % { name: celerized, type: type }
end
.uniq
.sort
add_to_output(output)
end
def emit_enums
output = ENUMS.map do |enum|
name = name_of(enum)
type = enum.fetch("allowed_values").sort.map(&:inspect).uniq.join(" | ")
FUNNY_NAMES[name] = name
ENUM_TPL % { name: name, type: type }
end
.uniq
.sort
add_to_output(output)
end
def emit_nodes()
nodes = NODES.map do |node|
tags = node.fetch("tags").sort.uniq
# Don't publish internal CeleryScript nodes:
next if tags.include?(:private)
tag_list = tags.join(", ")
name = name_of(node).to_s
bodies = node
.fetch("allowed_body_types")
.sort
.uniq
.map(&:to_s)
.map(&:camelize)
bt = bodies.any? ? "(#{bodies.join(" | ")})" : "never"
PUBLIC_NODES.push(name.camelize)
tpl_binding = {
body_types: bt,
camel_case: name.camelize,
docs: node.fetch("docs"),
snake_case: name,
tag_docs: "Tag properties: #{tag_list}."
}
one = NODE_START % tpl_binding
two = node.fetch("allowed_args").sort.map do |arg|
MIDDLE_CENTER % {
arg_name: arg.to_s,
arg_values: ARGS.fetch(arg)
.fetch("allowed_values")
.map(&:name)
.map { |x| FUNNY_NAMES[x] || x.camelize }
.join(" | ")
}
end
three = BOTTOM_END % tpl_binding
[one, two, three].flatten.join("\n")
end
.compact
.uniq
.join("\n")
add_to_output(nodes)
end
def emit_misc()
types = PUBLIC_NODES.sort.uniq.join(" | ")
tpl = "export type CeleryNode = #{types};\n"
add_to_output(tpl)
end
emit_constants()
emit_values()
emit_enums()
emit_nodes()
emit_misc()
save!

View File

@ -1,143 +0,0 @@
WARNING_HEADER = \
"""
// THIS INTERFACE WAS AUTO GENERATED ON #{Date.today}
// DO NOT EDIT THIS FILE.
// IT WILL BE OVERWRITTEN ON EVERY CELERYSCRIPT UPGRADE.
"""
HASH = Sequence::Corpus.as_json({})
OUTPUT = [WARNING_HEADER]
FILE_PATH = "latest_corpus.ts"
VALUES = HASH.fetch(:values)
VALUE_PREFIX = "CS"
VALUES_TPL = "export type %{name} = %{type};\n"
VALUES_OVERRIDE = HashWithIndifferentAccess.new(float: "number", integer: "number")
# There are some rule exceptions when generating the Typescript corpus.
FUNNY_NAMES = { "Example" => "CSExample" }
ENUMS = HASH.fetch(:enums)
ENUM_TPL = "export type %{name} = %{type};\n"
ARGS = HASH
.fetch(:args)
.reduce(HashWithIndifferentAccess.new) do |acc, arg|
acc[arg.fetch("name").to_s] = arg
acc
end
NODES = HASH.fetch(:nodes)
NODE_START = [ "export type %{camel_case}BodyItem = %{body_types};",
"/** %{docs} %{tag_docs} */",
"export interface %{camel_case} {",
" comment?: string | undefined;",
' kind: "%{snake_case}";',
" args: {", ].join("\n")
MIDDLE_CENTER = " %{arg_name}: %{arg_values};"
BOTTOM_END = [ " }",
" body?: %{camel_case}BodyItem[] | undefined;",
"}\n", ].join("\n")
CONSTANT_DECLR_HACK = {
LATEST_VERSION: Sequence::LATEST_VERSION,
DIGITAL: CeleryScriptSettingsBag::DIGITAL,
ANALOG: CeleryScriptSettingsBag::ANALOG,
}
CONSTANT_DECLR_HACK_TPL = "export const %{name} = %{value};\n"
PUBLIC_NODES = [] # Filled at runtime
def emit_constants()
CONSTANT_DECLR_HACK.map do |(name, value)|
konst = CONSTANT_DECLR_HACK_TPL % { name: name, value: value }
add_to_output(konst)
end
end
def add_to_output(string)
OUTPUT.push(string)
end
def save!
File.open(FILE_PATH, "w") { |f| f.write(OUTPUT.join("")) }
puts "Saved to #{FILE_PATH}"
end
def name_of(thing)
thing.fetch("name").to_s
end
def emit_values
output = VALUES.map do |val|
real_name = name_of(val)
capitalized = real_name.capitalize
celerized = VALUE_PREFIX + capitalized
FUNNY_NAMES[capitalized] = celerized
type = VALUES_OVERRIDE.fetch(real_name, real_name)
VALUES_TPL % { name: celerized, type: type }
end
.uniq
.sort
add_to_output(output)
end
def emit_enums
output = ENUMS.map do |enum|
name = name_of(enum)
type = enum.fetch("allowed_values").sort.map(&:inspect).uniq.join(" | ")
FUNNY_NAMES[name] = name
ENUM_TPL % { name: name, type: type }
end
.uniq
.sort
add_to_output(output)
end
def emit_nodes()
nodes = NODES.map do |node|
tags = node.fetch("tags").sort.uniq
# Don't publish internal CeleryScript nodes:
next if tags.include?(:private)
tag_list = tags.join(", ")
name = name_of(node).to_s
bodies = node
.fetch("allowed_body_types")
.sort
.uniq
.map(&:to_s)
.map(&:camelize)
bt = bodies.any? ? "(#{bodies.join(" | ")})" : "never"
PUBLIC_NODES.push(name.camelize)
tpl_binding = {
body_types: bt,
camel_case: name.camelize,
docs: node.fetch("docs"),
snake_case: name,
tag_docs: "Tag properties: #{tag_list}."
}
one = NODE_START % tpl_binding
two = node.fetch("allowed_args").sort.map do |arg|
MIDDLE_CENTER % {
arg_name: arg.to_s,
arg_values: ARGS.fetch(arg)
.fetch("allowed_values")
.map(&:name)
.map { |x| FUNNY_NAMES[x] || x.camelize }
.join(" | ")
}
end
three = BOTTOM_END % tpl_binding
[one, two, three].flatten.join("\n")
end
.compact
.uniq
.join("\n")
add_to_output(nodes)
end
def emit_misc()
types = PUBLIC_NODES.sort.uniq.join(" | ")
tpl = "export type CeleryNode = #{types};\n"
add_to_output(tpl)
end
emit_constants()
emit_values()
emit_enums()
emit_nodes()
emit_misc()
save!

View File

@ -45,7 +45,7 @@
"coveralls": "3.0.2",
"enzyme": "3.8.0",
"enzyme-adapter-react-16": "1.9.1",
"farmbot": "7.0.0-rc8",
"farmbot": "7.0.0-rc9",
"farmbot-toastr": "1.0.3",
"i18next": "12.1.0",
"jest": "24.1.0",

View File

@ -12,7 +12,9 @@ describe CeleryScript::Corpus do
})
check1 = CeleryScript::Checker.new(not_ok, corpus, device)
expect(check1.valid?).to eq(false)
expect(check1.error.message).to include("not a type of group")
expect(check1.error.message)
.to include('"Veggies" is not a type of point. '\
'Allowed values: ["GenericPointer", "ToolSlot", "Plant"]')
end
it "does not all `every_location` in `move_absolute`" do