forked from loafle/openapi-generator-original
[GdScript] Templates for GdScript (Godot 4) (#19267)
* feat(gdscript): sketch implementation of gdscript target language
This does not really work yet, but it's a start.
Results are not denormalized, no support for enums nor datetimes,
and thousands of other features are missing.
I still don't know how we are going to denormalize JSON+LD
without writing a whole GDScript lib for it…
* feat: add an exhaustive list of keywords reserved in GDScript
I've also provided the small python script I used to generate the list.
* refacto(gdscript): start using partials in templates
Whilst I'm racking my brains trying to figure out integration testing…
* test(gdscript): prepare a demo and integration testing
* fix(gdscript): do not use subclasses, use plain POGO
(plain ol' godot object)
One: I don't know how they work under-the-hood.
Two: I'm very confused over-the-hood.
Tri: We do not need them.
* refacto(gdscript): move demo files to their own directory
I know I'm making a lot of commits for not much,
but now I'm opening the sample files with Godot as well,
and doing unholy things with filesystems,
so I'm not taking any chances.
It's all going to be squashed anyway. :)
* fix(gdscript): sample as a Godot project
It works ! I can now write integration tests in GDScript.
The real work starts now.
/spend 25h
* feat(gdscript): serialize and send body params
The test suite is now past its first hurdle, the 415 HTTP status code,
and went straight into an unexpected error 500.
I suspect the server does not like me trying to set the pet id at 0,
because that's what we're trying to do right now.
Godot is crashing a lot, mostly because I don't know how to make Callable.NOOP
and my current solution hints at optional on_success and on_failure,
yet if we omit them the engine will ragequit.
* feat(gdscript): check request body for required yet missing properties
Now we'll get a nice error when we forget to set a required property.
The demo is now able to:
- connect
- create a user
- login as that user
- create a pet
* feat(gdscript): namespace core classes as well with apiPrefixName
This makes our usage of `class_name` a little more acceptable.
* feat(gdscript): support prefixes and suffixes for class names
This will crutch namespacing well enough for most uses.
* feat(gdscript): handle enums, naively
* feat(gdscript): support basic API endpoint param constraints
- minLength
- maxLength
- minItems
- maxItems
- minimum
- maximum
- pattern (no flags)
* feat(gdscript): handle header params and header customization
We also support serializing to application/x-www-form-urlencoded now.
Next up: DateTimes !
* feat(gdscript): handle Date and DateTime like Strings
There's no timezone support in Godot for DateTimes.
* feat(gdscript): support plain text responses
* feat(gdscript): support collections of models
Those are Arrays, not custom collection objects.
* feat(gdscript): configure default host from OAS
* feat(gdscript): some documentation and better config
We don't need no factories nor singletons ; config is enough.
* docs(gdscript): document usage a little
* feat(gdscript): add more reserved words, skip jsonld models and configure features
We can now generate a client for an OAS server running ApiPlatform (PHP).
* feat(gdscript): improve logging with a configurable log level
* feat(gdscript): add support for Basic Bearer and Header ApiKey
(but I can't find the `description` template handler)
* fix(gdscript)
Too late to amend >.<
* fix(gdscript)
dangsarnit
* chore(gdscript): clean up a sprint artifact
* fix: don't forget the HTTP error code when relevant
* feat: use Resource as base class for models
* fix. Default string values now with "quotes"
* temporary remove settings as godot api have changed
* kick ci
* docs: review gdscript java class
* feat: support for TLS, some refacto, some review
There's still a lot of holes, TODOs and FIXMEs.
* feat: experimental support of Request inline objects
The inline response objects are still not supported.
* feat(gdscript): support inline request and response objects
* chore(gdscript): review the templates
* fix(gdscript): unexpected nulls in default values
{{#if defaultValue}} evaluates to true for null if we call super here.
* refacto(gdscript): replace "bee" prefix by "bzz", use a constructor
Now we pass the config and the client via the constructor.
This reduces the area of the public surface a bit, for the better I think.
This commit also cleans up the class name shenanigans.
* fix(gdscript): add missing file
* test(gdscript): refactor the test project to use the generated lib as addon
Since there is no singleton in the generated client, the addon need not be enabled in the project configuration to be usable.
The --headless mode is broken for now, as things changed in Godot 4 since the beta.
* docs(gdscript): document petstore server ADR
* test(gdscript): add GUT and an integration test
We used the latest stable GUT, but a feature we're going to need was merged today, so we'll need to update it either to master or to the next release at some point.
* refacto(gdscript, breaking): use an ApiResponse object in success callbacks
/spent 6d since the beginning
* test(gdscript): update integration tests
/spend 2h
* docs(gdscript): explain the new ApiResponse
Also moving core templates to their own subdir, for clarity.
/spend 10m
* chore(gdscript): review, document, clean up
/spend 2h
* test(gdscript): test the delete operation as well
/spend 7m
* feat(gdscript): update GUT and exit with appropriate code
/spend 2h
* docs(gdscript): add Gdscript's PI
Hire me while I'm available ! :D
I'd rather code than make a CV.
* feat(gdscript): support reserved keywords
Also adding some more assertions,
and using our own OAS file now.
/spend 3h
* refacto(gdscript): use "base" instead of "bee"
/spend 1h
* feat(gdscript): improve descriptions
/spend 1h
* fix(gdscript): await before polling
Contributed by @jchu231
* docs(gdscript): review the template files
* docs(gdscript): review and generate docs
---------
Co-authored-by: Bagrat <b.saatsazov@gmail.com>
This commit is contained in:
committed by
GitHub
parent
86a18bfb62
commit
959cf1c3c9
@@ -998,6 +998,7 @@ Here is a list of template creators:
|
||||
* Groovy: @victorgit
|
||||
* Go: @wing328 [:heart:](https://www.patreon.com/wing328)
|
||||
* Go (rewritten in 2.3.0): @antihax
|
||||
* Godot (GDScript): @Goutte [:heart:](https://liberapay.com/Goutte)
|
||||
* Haskell (http-client): @jonschoning
|
||||
* Java (Feign): @davidkiss
|
||||
* Java (Retrofit): @0legg
|
||||
|
||||
35
bin/configs/gdscript-petstore.yaml
Normal file
35
bin/configs/gdscript-petstore.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
# Run this configuration to update the sample project used in integration testing:
|
||||
# bin/generate-samples.sh bin/configs/gdscript-petstore.yaml
|
||||
|
||||
generatorName: gdscript
|
||||
|
||||
# We output straight into an addon directory. (any addon name will work)
|
||||
# The addon need not be enabled in Project Settings to be used, for now,
|
||||
# but that may change, so as best practice you should enable it anyway.
|
||||
outputDir: samples/client/petstore/gdscript/addons/oas.petstore.client
|
||||
|
||||
# We have two test servers available.
|
||||
# See https://github.com/OpenAPITools/openapi-generator/wiki/Integration-Tests
|
||||
|
||||
# A: Newer, recommended echo server OAS, that we're failing for now:
|
||||
# Exception: Could not process model 'Bird'. Please make sure that your schema is correct!
|
||||
# Caused by: java.lang.RuntimeException: reserved word color not allowed
|
||||
# Perhaps try this again later, using another config file like gdscript-echo.yaml
|
||||
# > Later: this has been solved, we should now be able to use echo as well
|
||||
#inputSpec: modules/openapi-generator/src/test/resources/3_0/echo_api.yaml
|
||||
|
||||
# B: Older (legacy, deprecated) petstore server OAS
|
||||
inputSpec: modules/openapi-generator/src/test/resources/3_0/gdscript/petstore.yaml
|
||||
|
||||
templateDir: modules/openapi-generator/src/main/resources/gdscript
|
||||
|
||||
additionalProperties:
|
||||
# Timestamping the generated sample project would only add noise to git
|
||||
hideGenerationTimestamp: "true"
|
||||
# Since we're polluting the global namespace with class_name (it's really convenient),
|
||||
# we can use these affixes to namespace the generated classes.
|
||||
# Best make sure the words you use here are not part of the domain of Godot.
|
||||
apiNamePrefix: Demo
|
||||
modelNamePrefix: Demo
|
||||
modelNameSuffix: Model
|
||||
coreNamePrefix: Demo
|
||||
1704
docs/generators/gdscript.md
Normal file
1704
docs/generators/gdscript.md
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ org.openapitools.codegen.languages.ErlangServerCodegen
|
||||
org.openapitools.codegen.languages.ErlangServerDeprecatedCodegen
|
||||
org.openapitools.codegen.languages.FsharpFunctionsServerCodegen
|
||||
org.openapitools.codegen.languages.FsharpGiraffeServerCodegen
|
||||
org.openapitools.codegen.languages.GdscriptClientCodegen
|
||||
org.openapitools.codegen.languages.GoClientCodegen
|
||||
org.openapitools.codegen.languages.GoEchoServerCodegen
|
||||
org.openapitools.codegen.languages.GoServerCodegen
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# {{{openAPI.info.title}}} GDScript Client
|
||||
|
||||
{{#if appDescriptionWithNewLines}}
|
||||
{{{appDescriptionWithNewLines}}}
|
||||
{{/if}}
|
||||
|
||||
This *Godot 4* addon was automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
{{#if servers}}
|
||||
− Servers:
|
||||
{{#each servers}}
|
||||
- [{{#if this.description}}{{this.description}}{{else}}{{this.url}}{{/if}}]({{this.url}})
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
- API version: {{appVersion}}
|
||||
{{#unless hideGenerationTimestamp}}
|
||||
- Build date: {{generatedDate}}
|
||||
{{/unless}}
|
||||
- Build package: {{generatorClass}}
|
||||
{{#if infoUrl}}
|
||||
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}).
|
||||
{{/if}}
|
||||
|
||||
|
||||
## Requirements.
|
||||
|
||||
Godot `4.x`.
|
||||
|
||||
|
||||
## Installation & Usage
|
||||
|
||||
Copy the files into the `addon` directory of your Godot project.
|
||||
|
||||
Then, enable the addon in your project settings.
|
||||
|
||||
You can now use it anywhere in your code:
|
||||
|
||||
{{#with apiInfo}}
|
||||
{{#each apis}}
|
||||
{{#if @first}}
|
||||
{{#with operations}}
|
||||
{{#each operation}}
|
||||
{{#if @first}}
|
||||
{{>api_doc_example}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
|
||||
|
||||
## Customization
|
||||
|
||||
You can [override the templates](https://openapi-generator.tech/docs/templating/).
|
||||
|
||||
> 1. Grab templates with `openapi-generator author template -g gdscript --library gdscript`
|
||||
> 2. Hack around
|
||||
> 3. Regenerate using `--template <dir>` to target your custom templates dir
|
||||
|
||||
|
||||
## Documentation for API Endpoints
|
||||
|
||||
All URIs are relative to *{{basePath}}*
|
||||
|
||||
Class | Method | HTTP request | Description
|
||||
------------ | ------------- | ------------- | -------------
|
||||
{{#with apiInfo}}
|
||||
{{#each apis}}
|
||||
{{#with operations}}
|
||||
{{#each operation}}*{{classname}}* | [**{{operationIdSnakeCase}}**]({{apiDocPath}}{{classname}}.md#{{operationIdSnakeCase}}) | **{{httpMethod}}** {{path}} | {{#if summary}}{{summary}}{{/if}}
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
|
||||
|
||||
## Documentation For Models
|
||||
|
||||
{{#each models}}
|
||||
{{#with model}}
|
||||
- [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
|
||||
|
||||
## Documentation For Authorization
|
||||
|
||||
{{#unless authMethods}}
|
||||
All endpoints do not require authorization.
|
||||
{{/unless}}
|
||||
{{#each authMethods}}
|
||||
{{#if @last}} Authentication schemes defined for the API:{{/if}}
|
||||
## {{{name}}}
|
||||
|
||||
{{#if isApiKey}}
|
||||
- **Type**: API key
|
||||
- **API key parameter name**: {{{keyParamName}}}
|
||||
- **Location**: {{#if isKeyInQuery}}URL query string{{/if}}{{#if isKeyInHeader}}HTTP header{{/if}}
|
||||
{{/if}}
|
||||
{{#if isBasic}}
|
||||
{{#if isBasicBasic}}
|
||||
- **Type**: HTTP basic authentication
|
||||
{{/if}}
|
||||
{{#if isBasicBearer}}
|
||||
- **Type**: Bearer authentication{{#if bearerFormat}} ({{{bearerFormat}}}){{/if}}
|
||||
{{/if}}
|
||||
{{#if isHttpSignature}}
|
||||
- **Type**: HTTP signature authentication
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if isOAuth}}
|
||||
- **Type**: OAuth
|
||||
- **Flow**: {{{flow}}}
|
||||
- **Authorization URL**: {{{authorizationUrl}}}
|
||||
- **Scopes**: {{#unless scopes}}N/A{{/unless}}
|
||||
{{#each scopes}} - **{{{scope}}}**: {{#if description}}{{{description}}}{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `TLS handshake error: -9984`
|
||||
|
||||
https://github.com/godotengine/godot/issues/59080#issuecomment-1065973210
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
In here are the `handlebars` templates used to generate the `GDScript` client.
|
||||
|
||||
All files without the `.handlebars` extension (including this very `README.md` file) are ignored.
|
||||
|
||||
You can copy them all (or parts) and override them as needed.
|
||||
|
||||
|
||||
## Domain Overview
|
||||
|
||||
### ApiBee
|
||||
|
||||
Base class for all Api endpoints classes.
|
||||
Holds most of the nitty-gritty.
|
||||
|
||||
|
||||
### ApiConfig
|
||||
|
||||
Reusable configuration (host, port, etc.) for Apis, injected into their constructor.
|
||||
|
||||
|
||||
### ApiError
|
||||
|
||||
Godot does not have an `Exception` (`try / catch`) mechanism, by design.
|
||||
|
||||
> It actually makes _some_ sense in the volatile environment that are video games,
|
||||
> due to their gigantic amount of user inputs and userland data,
|
||||
> inherent complexity, low stakes, and entertainment value of glitches.
|
||||
|
||||
Therefore, whenever there's trouble in paradise, we pass around an `ApiError` object. (a `RefCounted`, don't worry about garbage collection)
|
||||
|
||||
|
||||
### ApiResponse
|
||||
|
||||
A wrapper for an API Response, used in callbacks.
|
||||
Holds the HTTP components of the Response, as well as the deserialized `data` (if any).
|
||||
|
||||
|
||||
## Extending
|
||||
|
||||
Most classes can be configured to extend your own class.
|
||||
Override the `partials/*_parent_class.handlebars` to define them.
|
||||
@@ -0,0 +1,206 @@
|
||||
{{>partials/api_statement_extends}}
|
||||
{{>partials/api_statement_class_name}}
|
||||
|
||||
|
||||
{{>partials/api_headers}}
|
||||
|
||||
func _bzz_get_api_name() -> String:
|
||||
return "{{classname}}"
|
||||
|
||||
{{#with operations}}
|
||||
{{#each operation}}
|
||||
|
||||
{{#if isDeprecated}}
|
||||
# /!. DEPRECATED
|
||||
{{/if}}
|
||||
# Operation {{{operationId}}} → {{{httpMethod}}} {{{path}}}
|
||||
{{#if summary}}
|
||||
# {{{summary}}}
|
||||
{{/if}}
|
||||
{{#if description}}
|
||||
#
|
||||
# {{{description}}}
|
||||
{{/if}}
|
||||
{{#if notes}}
|
||||
#
|
||||
# {{{notes}}}
|
||||
{{/if}}
|
||||
func {{operationIdSnakeCase}}(
|
||||
{{>partials/api_method_params}}
|
||||
):
|
||||
{{#if isDeprecated}}
|
||||
push_warning("Usage of `{{operationIdSnakeCase}}()` is deprecated.")
|
||||
{{/if}}
|
||||
|
||||
{{#each allParams}}
|
||||
{{#if hasValidation}}
|
||||
# Validate param `{{paramName}}` constraints
|
||||
{{#if maxLength}}
|
||||
{{#if isString}}
|
||||
if ({{paramName}} is String) and {{paramName}}.length() > {{maxLength}}:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
#error.internal_code = ERR_INVALID_PARAMETER
|
||||
error.identifier = "{{operationIdSnakeCase}}.param.validation.max_length"
|
||||
error.message = "Invalid length for `{{paramName}}`, must be smaller than or equal to {{maxLength}}."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if minLength}}
|
||||
{{#if isString}}
|
||||
if ({{paramName}} is String) and {{paramName}}.length() < {{minLength}}:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "{{operationIdSnakeCase}}.param.validation.min_length"
|
||||
error.message = "Invalid length for `{{paramName}}`, must be greater than or equal to {{minLength}}."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if maximum}}
|
||||
{{! isNumeric / isNumber yields false yet isLong yields true }}
|
||||
{{! not sure if bug 'cause of handlebars or not ; let's skip }}
|
||||
{{!#if isNumeric}}
|
||||
if {{paramName}} >{{#if exclusiveMaximum}}={{/if}} {{maximum}}:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "{{operationIdSnakeCase}}.param.validation.maximum"
|
||||
error.message = "Invalid value for `{{paramName}}`, must be smaller than{{#unless exclusiveMaximum}} or equal to{{/unless}} {{maximum}}."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{!/if}}
|
||||
{{/if}}
|
||||
{{#if minimum}}
|
||||
{{!#if isNumeric}}
|
||||
if {{paramName}} <{{#if exclusiveMinimum}}={{/if}} {{minimum}}:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "{{operationIdSnakeCase}}.param.validation.minimum"
|
||||
error.message = "Invalid value for `{{paramName}}`, must be greater than{{#unless exclusiveMinimum}} or equal to{{/unless}} {{minimum}}."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{!/if}}
|
||||
{{/if}}
|
||||
{{#if maxItems}}
|
||||
{{#if isArray}}
|
||||
if ({{paramName}} is Array) and {{paramName}}.size() > {{maxItems}}:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "{{operationIdSnakeCase}}.param.validation.max_items"
|
||||
error.message = "Invalid array size for `{{paramName}}`, must hold at most {{maxItems}} elements."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if minItems}}
|
||||
{{#if isArray}}
|
||||
if ({{paramName}} is Array) and {{paramName}}.size() < {{minItems}}:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "{{operationIdSnakeCase}}.param.validation.min_items"
|
||||
error.message = "Invalid array size for `{{paramName}}`, must hold at least {{minItems}} elements."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if pattern}}
|
||||
var bzz_{{paramName}}_regex := RegEx.new()
|
||||
{{! These regex trimming shenanigans will fail if regex has flags }}
|
||||
{{! A solution would be to use another RegEx to extract that data from the pattern ? }}
|
||||
bzz_{{paramName}}_regex.compile("{{{pattern}}}".trim_prefix('/').trim_suffix('/'))
|
||||
if not bzz_{{paramName}}_regex.search(str({{paramName}})):
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "{{operationIdSnakeCase}}.param.validation.pattern"
|
||||
error.message = "Invalid value for `{{paramName}}`, must conform to the pattern `{{{pattern}}}`."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
# Convert the String HTTP method to a Constant Godot understands
|
||||
var bzz_method := self._bzz_convert_http_method("{{httpMethod}}")
|
||||
|
||||
# Compute the URL path to the API resource
|
||||
var bzz_path := "{{{contextPath}}}{{{path}}}"{{#each pathParams}}.replace("{" + "{{baseName}}" + "}", _bzz_urlize_path_param({{{paramName}}})){{/each}}
|
||||
|
||||
# Collect the headers
|
||||
var bzz_headers := Dictionary()
|
||||
{{#each headerParams}}
|
||||
bzz_headers["{{baseName}}"] = {{paramName}}
|
||||
{{/each}}
|
||||
{{#if consumes}}
|
||||
var bzz_mimes_consumable_by_server := [{{#each consumes}}'{{{mediaType}}}'{{#unless @last}}, {{/unless}}{{/each}}]
|
||||
var bzz_found_producible_mime := false
|
||||
for bzz_mime in BZZ_PRODUCIBLE_CONTENT_TYPES:
|
||||
if bzz_mime in bzz_mimes_consumable_by_server:
|
||||
bzz_headers["Content-Type"] = bzz_mime
|
||||
bzz_found_producible_mime = true
|
||||
break
|
||||
if not bzz_found_producible_mime:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "{{operationIdSnakeCase}}.headers.content_type"
|
||||
error.message = "That endpoint only accepts %s as content type(s) and none are supported by this client."
|
||||
on_failure.call(error)
|
||||
return
|
||||
{{/if}}
|
||||
{{#if produces}}
|
||||
var bzz_mimes_produced_by_server := [{{#each produces}}'{{{mediaType}}}'{{#unless @last}}, {{/unless}}{{/each}}]
|
||||
for bzz_mime in BZZ_CONSUMABLE_CONTENT_TYPES:
|
||||
if bzz_mime in bzz_mimes_produced_by_server:
|
||||
bzz_headers["Accept"] = bzz_mime
|
||||
break
|
||||
{{/if}}
|
||||
|
||||
# Collect the query parameters
|
||||
# Note: we do not support multiple values for a single param (for now), nor arrays
|
||||
var bzz_query := Dictionary()
|
||||
{{#each queryParams}}
|
||||
bzz_query["{{baseName}}"] = {{paramName}}
|
||||
{{/each}}
|
||||
|
||||
var bzz_body = null
|
||||
{{#if bodyParams}}
|
||||
{{#each bodyParams}}
|
||||
{{! What should happen here when there are multiple body params? for now last wins }}
|
||||
bzz_body = {{paramName}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{#if formParams}}
|
||||
bzz_body = Dictionary()
|
||||
{{#each formParams}}
|
||||
bzz_body["{{paramName}}"] = {{paramName}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
self._bzz_request(
|
||||
bzz_method, bzz_path, bzz_headers, bzz_query, bzz_body,
|
||||
func(bzz_response):
|
||||
{{#with returnProperty}}
|
||||
{{#if isArray}}
|
||||
bzz_response.data = {{>partials/complex_type}}.bzz_denormalize_multiple(bzz_response.data)
|
||||
{{else if isModel}}
|
||||
bzz_response.data = {{>partials/data_type}}.bzz_denormalize_single(bzz_response.data)
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
on_success.call(bzz_response)
|
||||
,
|
||||
func(bzz_error):
|
||||
on_failure.call(bzz_error)
|
||||
, # ざわ‥
|
||||
)
|
||||
|
||||
|
||||
func {{operationIdSnakeCase}}_threaded(
|
||||
{{>partials/api_method_params}}
|
||||
) -> Thread:
|
||||
var bzz_thread := Thread.new()
|
||||
var bzz_callable := Callable(self, "{{operationIdSnakeCase}}")
|
||||
bzz_callable.bind(
|
||||
{{#each allParams}}
|
||||
{{paramName}},
|
||||
{{/each}}
|
||||
on_success,
|
||||
on_failure,
|
||||
)
|
||||
bzz_thread.start(bzz_callable)
|
||||
return bzz_thread
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/with}}
|
||||
@@ -0,0 +1,64 @@
|
||||
<a name="__pageTop"></a>
|
||||
# {{{classname}}} { #{{classname}} }
|
||||
{{#if description}}{{description}}{{/if}}
|
||||
|
||||
All URIs are relative to *{{basePath}}*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
{{#with operations}}
|
||||
{{#each operation}}
|
||||
[**{{operationIdSnakeCase}}**](#{{operationIdSnakeCase}}) | **{{httpMethod}}** `{{path}}` | {{#if summary}}{{summary}}{{/if}}
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
|
||||
{{#with operations}}
|
||||
{{#each operation}}
|
||||
# **{{{operationIdSnakeCase}}}** { #{{{operationIdSnakeCase}}} }
|
||||
<a name="{{{operationIdSnakeCase}}}"></a>
|
||||
|
||||
> `{{operationIdSnakeCase}}({{#each allParams}}{{paramName}}{{#if required}}{{#if dataType}}: {{{dataType}}}{{/if}}{{/if}}{{#unless required}} = {{#if defaultValue}}{{{defaultValue}}}{{/if}}{{#unless defaultValue}}null{{/unless}}{{/unless}},{{/each}} on_success: Callable, on_failure: Callable)`
|
||||
|
||||
{{#if summary}}{{{summary}}}{{/if}}
|
||||
|
||||
{{#if notes}}{{{notes}}}{{/if}}
|
||||
|
||||
### Example
|
||||
|
||||
{{#if hasAuthMethods}}
|
||||
{{#each authMethods}}
|
||||
{{#if isBasic}}
|
||||
{{#if isBasicBasic}}
|
||||
* Basic Authentication (`{{name}}`)
|
||||
{{/if}}
|
||||
{{#if isBasicBearer}}
|
||||
* Bearer{{#if bearerFormat}} ({{{bearerFormat}}}){{/if}} Authentication (`{{name}}`)
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if isApiKey}}
|
||||
* Api Key Authentication (`{{name}}`)
|
||||
{{/if}}
|
||||
{{#if isOAuth}}
|
||||
* OAuth Authentication (`{{name}}`)
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{> api_doc_example }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
### Authorization
|
||||
|
||||
{{#each authMethods}}
|
||||
[{{{name}}}](../README.md#{{name}}){{#unless @last}}, {{/unless}}
|
||||
{{else}}
|
||||
No authorization required.
|
||||
{{/each}}
|
||||
|
||||
[[Back to top]](#__pageTop) \
|
||||
[[Back to API list]](../README.md#documentation-for-api-endpoints) \
|
||||
[[Back to Model list]](../README.md#documentation-for-models) \
|
||||
[[Back to README]](../README.md) \
|
||||
|
||||
{{/with}}
|
||||
@@ -0,0 +1,45 @@
|
||||
```gdscript
|
||||
|
||||
# Customize configuration
|
||||
var config := {{>partials/api_config_class_name}}.new()
|
||||
config.host = "localhost"
|
||||
config.port = 8080
|
||||
#config.tls_enabled = true
|
||||
#config.trusted_chain = preload("res://my_cert_chain.crt")
|
||||
|
||||
# Instantiate the api
|
||||
var api = {{{classname}}}.new(config)
|
||||
# You can also provide your own HTTPClient, to re-use it across apis.
|
||||
#var api = {{{classname}}}.new(config, client)
|
||||
|
||||
{{#each bodyParams}}
|
||||
{{#if dataType}}
|
||||
{{#if isModel}}
|
||||
var {{paramName}} = {{dataType}}.new()
|
||||
# … fill model {{paramName}} with data
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
# Invoke an endpoint
|
||||
api.{{{operationIdSnakeCase}}}(
|
||||
{{#each allParams}}
|
||||
# {{paramName}}{{#if dataType}}: {{dataType}}{{/if}}{{#if defaultValue}} = {{{defaultValue}}}{{/if}}{{#if example}} Eg: {{{example}}}{{/if}}
|
||||
{{~#if description}}
|
||||
|
||||
# {{{description}}}{{/if}}
|
||||
{{paramName}},
|
||||
{{/each}}
|
||||
# On Success
|
||||
func(response):{{#with returnProperty}} # response is {{>partials/api_response_class_name}}{{/with}}
|
||||
prints("Success!", "{{operationIdSnakeCase}}", response)
|
||||
{{#with returnProperty}}assert(response.data is {{>partials/complex_type}}){{/with}}
|
||||
pass # do things, make stuff
|
||||
,
|
||||
# On Error
|
||||
func(error): # error is {{>partials/api_error_class_name}}
|
||||
push_error(str(error))
|
||||
,
|
||||
)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,502 @@
|
||||
extends {{>partials/api_base_parent_class}}
|
||||
class_name {{>partials/api_base_class_name}}
|
||||
|
||||
{{>partials/disclaimer_autogenerated}}
|
||||
|
||||
# Base class for all generated API endpoints
|
||||
# ==========================================
|
||||
#
|
||||
# Every property/method defined here may collide with userland,
|
||||
# so these are all listed and excluded in our CodeGen Java file.
|
||||
# We want to keep the amount of renaming to a minimum, though.
|
||||
# Therefore, we use the _bzz_ prefix, even if awkward.
|
||||
|
||||
|
||||
const BZZ_CONTENT_TYPE_TEXT := "text/plain"
|
||||
const BZZ_CONTENT_TYPE_HTML := "text/html"
|
||||
const BZZ_CONTENT_TYPE_JSON := "application/json"
|
||||
const BZZ_CONTENT_TYPE_FORM := "application/x-www-form-urlencoded"
|
||||
const BZZ_CONTENT_TYPE_JSONLD := "application/json+ld" # unsupported (for now)
|
||||
const BZZ_CONTENT_TYPE_XML := "application/xml" # unsupported (for now)
|
||||
|
||||
# From this client's point of view.
|
||||
# Adding a content type here won't magically make the client support it, but you may reorder.
|
||||
# These are sorted by decreasing preference. (first → preferred)
|
||||
const BZZ_PRODUCIBLE_CONTENT_TYPES := [
|
||||
BZZ_CONTENT_TYPE_JSON,
|
||||
BZZ_CONTENT_TYPE_FORM,
|
||||
]
|
||||
|
||||
# From this client's point of view.
|
||||
# Adding a content type here won't magically make the client support it, but you may reorder.
|
||||
# These are sorted by decreasing preference. (first → preferred)
|
||||
const BZZ_CONSUMABLE_CONTENT_TYPES := [
|
||||
BZZ_CONTENT_TYPE_JSON,
|
||||
]
|
||||
|
||||
|
||||
# Godot's HTTP Client this Api instance is using.
|
||||
# If none was set (by you), we'll lazily make one.
|
||||
var _bzz_client: HTTPClient:
|
||||
set(value):
|
||||
_bzz_client = value
|
||||
get:
|
||||
if not _bzz_client:
|
||||
_bzz_client = HTTPClient.new()
|
||||
return _bzz_client
|
||||
|
||||
|
||||
# General configuration that can be shared across Api instances for convenience.
|
||||
# If no configuration was provided, we'll lazily make one with defaults,
|
||||
# but you probably want to make your own with your own domain and scheme.
|
||||
var _bzz_config: {{>partials/api_config_class_name}}:
|
||||
set(value):
|
||||
_bzz_config = value
|
||||
get:
|
||||
if not _bzz_config:
|
||||
_bzz_config = {{>partials/api_config_class_name}}.new()
|
||||
return _bzz_config
|
||||
|
||||
|
||||
# Useful in logs
|
||||
var _bzz_name: String:
|
||||
get:
|
||||
return _bzz_get_api_name()
|
||||
|
||||
|
||||
# Constructor, where you probably want to inject your configuration,
|
||||
# and as Godot recommends re-using HTTP clients, your client as well.
|
||||
func _init(config : {{>partials/api_config_class_name}} = null, client : HTTPClient = null):
|
||||
if config != null:
|
||||
self._bzz_config = config
|
||||
if client != null:
|
||||
self._bzz_client = client
|
||||
|
||||
|
||||
{{! We'll probably only use this for logging. }}
|
||||
{{! Each Api child can define its own, and it should be similar to class_name. }}
|
||||
{{! https://github.com/godotengine/godot/issues/21789 }}
|
||||
func _bzz_get_api_name() -> String:
|
||||
return "ApiBee"
|
||||
|
||||
|
||||
func _bzz_next_loop_iteration():
|
||||
# I can't find `idle_frame` in 4.0, but we probably want idle_frame here
|
||||
return Engine.get_main_loop().process_frame
|
||||
|
||||
|
||||
func _bzz_connect_client_if_needed(
|
||||
on_success: Callable, # func()
|
||||
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
|
||||
#finally: Callable,
|
||||
):
|
||||
if (
|
||||
self._bzz_client.get_status() == HTTPClient.STATUS_CONNECTED
|
||||
or
|
||||
self._bzz_client.get_status() == HTTPClient.STATUS_RESOLVING
|
||||
or
|
||||
self._bzz_client.get_status() == HTTPClient.STATUS_CONNECTING
|
||||
or
|
||||
self._bzz_client.get_status() == HTTPClient.STATUS_REQUESTING
|
||||
or
|
||||
self._bzz_client.get_status() == HTTPClient.STATUS_BODY
|
||||
):
|
||||
on_success.call()
|
||||
|
||||
var connecting := self._bzz_client.connect_to_host(
|
||||
self._bzz_config.host, self._bzz_config.port, self._bzz_config.tls_options
|
||||
)
|
||||
if connecting != OK:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.internal_code = connecting
|
||||
error.identifier = "apibee.connect_to_host.failure"
|
||||
error.message = "%s: failed to connect to `%s' port `%d' with error: %s" % [
|
||||
_bzz_name, self._bzz_config.host, self._bzz_config.port,
|
||||
_bzz_httpclient_status_string(connecting),
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
|
||||
# Wait until resolved and connected.
|
||||
while (
|
||||
self._bzz_client.get_status() == HTTPClient.STATUS_CONNECTING
|
||||
or
|
||||
self._bzz_client.get_status() == HTTPClient.STATUS_RESOLVING
|
||||
):
|
||||
self._bzz_client.poll()
|
||||
self._bzz_config.log_debug("Connecting…")
|
||||
if self._bzz_config.polling_interval_ms:
|
||||
OS.delay_msec(self._bzz_config.polling_interval_ms)
|
||||
await _bzz_next_loop_iteration()
|
||||
|
||||
var connected := self._bzz_client.get_status()
|
||||
if connected != HTTPClient.STATUS_CONNECTED:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.internal_code = connected as Error
|
||||
error.identifier = "apibee.connect_to_host.status_failure"
|
||||
error.message = "%s: failed to connect to `%s' port `%d' : %s" % [
|
||||
_bzz_name, self._bzz_config.host, self._bzz_config.port,
|
||||
_bzz_httpclient_status_string(connected),
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
|
||||
on_success.call()
|
||||
|
||||
|
||||
func _bzz_request(
|
||||
method: int, # one of HTTPClient.METHOD_XXXXX
|
||||
path: String,
|
||||
headers: Dictionary,
|
||||
query: Dictionary,
|
||||
body, # Variant that will be serialized and sent
|
||||
on_success: Callable, # func(response: {{>partials/api_response_class_name}})
|
||||
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
|
||||
):
|
||||
# This method does not handle full deserialization, it only handles decode and not denormalization.
|
||||
# Denormalization is handled in each generated API endpoint in the on_success callable of this method.
|
||||
|
||||
_bzz_request_text(
|
||||
method, path, headers, query, body,
|
||||
func(response):
|
||||
var mime: String = response.headers['Mime']
|
||||
var decodedBody # Variant
|
||||
|
||||
# Isn't there a match/case now in Gdscript?
|
||||
if BZZ_CONTENT_TYPE_TEXT == mime:
|
||||
decodedBody = response.body
|
||||
elif BZZ_CONTENT_TYPE_HTML == mime:
|
||||
decodedBody = response.body
|
||||
elif BZZ_CONTENT_TYPE_JSON == mime:
|
||||
var parser := JSON.new()
|
||||
var parsing := parser.parse(response.body)
|
||||
if OK != parsing:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.internal_code = parsing
|
||||
error.identifier = "apibee.decode.cannot_parse_json"
|
||||
error.response_code = response.code
|
||||
error.response = response
|
||||
error.message = "%s: failed to parse JSON response at line %d.\n%s" % [
|
||||
_bzz_name, parser.get_error_line(), parser.get_error_message()
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
decodedBody = parser.data
|
||||
else:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.internal_code = ERR_INVALID_DATA
|
||||
error.identifier = "apibee.decode.mime_type_unsupported"
|
||||
error.response_code = response.code
|
||||
error.response = response
|
||||
error.message = "%s: mime type `%s' is not supported (yet -- MRs welcome)" % [
|
||||
_bzz_name, mime
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
|
||||
response.data = decodedBody
|
||||
on_success.call(response)
|
||||
,
|
||||
func(error):
|
||||
on_failure.call(error)
|
||||
,
|
||||
)
|
||||
|
||||
|
||||
func _bzz_request_text(
|
||||
method: int, # one of HTTPClient.METHOD_XXXXX
|
||||
path: String,
|
||||
headers: Dictionary,
|
||||
query: Dictionary,
|
||||
body, # Variant that will be serialized
|
||||
on_success: Callable, # func(response: {{>partials/api_response_class_name}})
|
||||
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
|
||||
):
|
||||
_bzz_connect_client_if_needed(
|
||||
func():
|
||||
_bzz_do_request_text(method, path, headers, query, body, on_success, on_failure)
|
||||
,
|
||||
func(error):
|
||||
on_failure.call(error)
|
||||
,
|
||||
)
|
||||
|
||||
|
||||
func _bzz_do_request_text(
|
||||
method: int, # one of HTTPClient.METHOD_XXXXX
|
||||
path: String,
|
||||
headers: Dictionary,
|
||||
query: Dictionary,
|
||||
body, # Variant that will be serialized
|
||||
on_success: Callable, # func(response: {{>partials/api_response_class_name}})
|
||||
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
|
||||
):
|
||||
|
||||
headers = headers.duplicate(true)
|
||||
headers.merge(self._bzz_config.headers_base)
|
||||
headers.merge(self._bzz_config.headers_override, true)
|
||||
|
||||
var body_normalized = body
|
||||
if body is Object:
|
||||
if body.has_method('bzz_collect_missing_properties'):
|
||||
var missing_properties : Array = body.bzz_collect_missing_properties()
|
||||
if missing_properties:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "apibee.request.body.missing_properties"
|
||||
error.message = "%s: `%s' is missing required properties %s." % [
|
||||
_bzz_name, body.bzz_class_name, missing_properties
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
if body.has_method('bzz_normalize'):
|
||||
body_normalized = body.bzz_normalize()
|
||||
|
||||
var body_serialized := ""
|
||||
var content_type := self._bzz_get_content_type(headers)
|
||||
if content_type == BZZ_CONTENT_TYPE_JSON:
|
||||
body_serialized = JSON.stringify(body_normalized)
|
||||
elif content_type == BZZ_CONTENT_TYPE_FORM:
|
||||
body_serialized = self._bzz_client.query_string_from_dict(body_normalized)
|
||||
else:
|
||||
# TODO: Handle other serialization schemes (json+ld, xml…)
|
||||
push_warning("Unsupported content-type `%s`." % content_type)
|
||||
|
||||
var path_queried := path
|
||||
var query_string := self._bzz_client.query_string_from_dict(query)
|
||||
if query_string:
|
||||
path_queried = "%s?%s" % [path, query_string]
|
||||
|
||||
{{! Godot HTTP Client expects an array of strings, not a dictionary }}
|
||||
var headers_for_godot := Array() # of String
|
||||
for key in headers:
|
||||
headers_for_godot.append("%s: %s" % [key, headers[key]])
|
||||
|
||||
self._bzz_config.log_info("%s: REQUEST %s %s" % [_bzz_name, method, path_queried])
|
||||
if not headers.is_empty():
|
||||
self._bzz_config.log_debug("→ HEADERS: %s" % [str(headers)])
|
||||
if body_serialized:
|
||||
self._bzz_config.log_debug("→ BODY: \n%s" % [body_serialized])
|
||||
|
||||
var requesting := self._bzz_client.request(method, path_queried, headers_for_godot, body_serialized)
|
||||
if requesting != OK:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.internal_code = requesting
|
||||
error.identifier = "apibee.request.failure"
|
||||
error.message = "%s: failed to request to path `%s'." % [
|
||||
_bzz_name, path
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
|
||||
# Keep polling for as long as the request is being processed.
|
||||
while self._bzz_client.get_status() == HTTPClient.STATUS_REQUESTING:
|
||||
self._bzz_config.log_debug("Requesting…")
|
||||
if self._bzz_config.polling_interval_ms:
|
||||
OS.delay_msec(self._bzz_config.polling_interval_ms)
|
||||
await _bzz_next_loop_iteration()
|
||||
self._bzz_client.poll()
|
||||
|
||||
if not self._bzz_client.has_response():
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "apibee.request.no_response"
|
||||
error.message = "%s: request to `%s' returned no response whatsoever. (status=%d)" % [
|
||||
_bzz_name, path, self._bzz_client.get_status(),
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
|
||||
var response := {{>partials/api_response_class_name}}.new()
|
||||
#response.collect_meta_from_client(self._bzz_client) # to refactor
|
||||
response.code = self._bzz_client.get_response_code()
|
||||
response.headers = self._bzz_client.get_response_headers_as_dictionary()
|
||||
# FIXME: extract from headers "Content-Type": "application/json; charset=utf-8"
|
||||
# Perhaps use a method of {{>partials/api_response_class_name}} for this?
|
||||
var encoding := "utf-8"
|
||||
var mime := "application/json"
|
||||
response.headers['Encoding'] = encoding
|
||||
response.headers['Mime'] = mime
|
||||
|
||||
#response.collect_body_from_client(self._bzz_client, self._bzz_config) # to refactor
|
||||
# TODO: cap the size of this, perhaps?
|
||||
var response_bytes := PackedByteArray()
|
||||
|
||||
while self._bzz_client.get_status() == HTTPClient.STATUS_BODY:
|
||||
var chunk = self._bzz_client.read_response_body_chunk()
|
||||
if chunk.size() == 0: # Got nothing, wait for buffers to fill a bit.
|
||||
if self._bzz_config.polling_interval_ms:
|
||||
OS.delay_usec(self._bzz_config.polling_interval_ms)
|
||||
await _bzz_next_loop_iteration()
|
||||
else: # Yummy data has arrived
|
||||
response_bytes = response_bytes + chunk
|
||||
self._bzz_client.poll()
|
||||
|
||||
self._bzz_config.log_info("%s: RESPONSE %d (%d bytes)" % [
|
||||
_bzz_name, response.code, response_bytes.size()
|
||||
])
|
||||
if not response.headers.is_empty():
|
||||
self._bzz_config.log_debug("→ HEADERS: %s" % str(response.headers))
|
||||
|
||||
var response_text: String
|
||||
if encoding == "utf-8":
|
||||
response_text = response_bytes.get_string_from_utf8()
|
||||
elif encoding == "utf-16":
|
||||
response_text = response_bytes.get_string_from_utf16()
|
||||
elif encoding == "utf-32":
|
||||
response_text = response_bytes.get_string_from_utf32()
|
||||
else:
|
||||
response_text = response_bytes.get_string_from_ascii()
|
||||
|
||||
if response_text:
|
||||
self._bzz_config.log_debug("→ BODY: \n%s" % response_text)
|
||||
response.body = response_text
|
||||
|
||||
if response.code >= 500:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.internal_code = ERR_PRINTER_ON_FIRE
|
||||
error.response_code = response.code
|
||||
error.response = response
|
||||
error.identifier = "apibee.response.5xx"
|
||||
error.message = "%s: request to `%s' made the server hiccup with a %d." % [
|
||||
_bzz_name, path, response.code
|
||||
]
|
||||
error.message += "\n%s" % [
|
||||
_bzz_format_error_response(response_text)
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
elif response.code >= 400:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "apibee.response.4xx"
|
||||
error.response_code = response.code
|
||||
error.response = response
|
||||
error.message = "%s: request to `%s' was denied with a %d." % [
|
||||
_bzz_name, path, response.code
|
||||
]
|
||||
error.message += "\n%s" % [
|
||||
_bzz_format_error_response(response_text)
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
elif response.code >= 300:
|
||||
var error := {{>partials/api_error_class_name}}.new()
|
||||
error.identifier = "apibee.response.3xx"
|
||||
error.response_code = response.code
|
||||
error.response = response
|
||||
error.message = "%s: request to `%s' was redirected with a %d. We do not support redirects in that client yet." % [
|
||||
_bzz_name, path, response.code
|
||||
]
|
||||
on_failure.call(error)
|
||||
return
|
||||
|
||||
# Should we close ?
|
||||
#self._bzz_client.close()
|
||||
|
||||
on_success.call(response)
|
||||
|
||||
|
||||
func _bzz_convert_http_method(method: String) -> int:
|
||||
match method:
|
||||
'GET': return HTTPClient.METHOD_GET
|
||||
'POST': return HTTPClient.METHOD_POST
|
||||
'PUT': return HTTPClient.METHOD_PUT
|
||||
'PATCH': return HTTPClient.METHOD_PATCH
|
||||
'DELETE': return HTTPClient.METHOD_DELETE
|
||||
'CONNECT': return HTTPClient.METHOD_CONNECT
|
||||
'HEAD': return HTTPClient.METHOD_HEAD
|
||||
'MAX': return HTTPClient.METHOD_MAX
|
||||
'OPTIONS': return HTTPClient.METHOD_OPTIONS
|
||||
'TRACE': return HTTPClient.METHOD_TRACE
|
||||
_:
|
||||
push_error("%s: unknown http method `%s`, assuming GET." % [
|
||||
_bzz_name, method
|
||||
])
|
||||
return HTTPClient.METHOD_GET
|
||||
|
||||
|
||||
func _bzz_urlize_path_param(anything) -> String:
|
||||
var serialized := _bzz_escape_path_param(str(anything))
|
||||
return serialized
|
||||
|
||||
|
||||
func _bzz_escape_path_param(value: String) -> String:
|
||||
# TODO: escape for URL
|
||||
return value
|
||||
|
||||
|
||||
func _bzz_get_content_type(headers: Dictionary) -> String:
|
||||
if headers.has("Content-Type"):
|
||||
return headers["Content-Type"]
|
||||
return BZZ_PRODUCIBLE_CONTENT_TYPES[0]
|
||||
|
||||
|
||||
func _bzz_format_error_response(response: String) -> String:
|
||||
# TODO: handle other (de)serialization schemes
|
||||
var parser := JSON.new()
|
||||
var parsing := parser.parse(response)
|
||||
if OK != parsing:
|
||||
return response
|
||||
if not (parser.data is Dictionary):
|
||||
return response
|
||||
var s := "ERROR"
|
||||
if parser.data.has("code"):
|
||||
s += " %d" % parser.data['code']
|
||||
if parser.data.has("message"):
|
||||
s += "\n%s" % parser.data['message']
|
||||
else:
|
||||
return response
|
||||
return s
|
||||
|
||||
|
||||
func _bzz_httpclient_status_info(status: int) -> Dictionary:
|
||||
# At some point Godot ought to natively implement this and we won't need this "shim" anymore.
|
||||
match status:
|
||||
HTTPClient.STATUS_DISCONNECTED: return {
|
||||
"name": "STATUS_DISCONNECTED",
|
||||
"description": "Disconnected from the server."
|
||||
}
|
||||
HTTPClient.STATUS_RESOLVING: return {
|
||||
"name": "STATUS_RESOLVING",
|
||||
"description": "Currently resolving the hostname for the given URL into an IP."
|
||||
}
|
||||
HTTPClient.STATUS_CANT_RESOLVE: return {
|
||||
"name": "STATUS_CANT_RESOLVE",
|
||||
"description": "DNS failure: Can't resolve the hostname for the given URL."
|
||||
}
|
||||
HTTPClient.STATUS_CONNECTING: return {
|
||||
"name": "STATUS_CONNECTING",
|
||||
"description": "Currently connecting to server."
|
||||
}
|
||||
HTTPClient.STATUS_CANT_CONNECT: return {
|
||||
"name": "STATUS_CANT_CONNECT",
|
||||
"description": "Can't connect to the server."
|
||||
}
|
||||
HTTPClient.STATUS_CONNECTED: return {
|
||||
"name": "STATUS_CONNECTED",
|
||||
"description": "Connection established."
|
||||
}
|
||||
HTTPClient.STATUS_REQUESTING: return {
|
||||
"name": "STATUS_REQUESTING",
|
||||
"description": "Currently sending request."
|
||||
}
|
||||
HTTPClient.STATUS_BODY: return {
|
||||
"name": "STATUS_BODY",
|
||||
"description": "HTTP body received."
|
||||
}
|
||||
HTTPClient.STATUS_CONNECTION_ERROR: return {
|
||||
"name": "STATUS_CONNECTION_ERROR",
|
||||
"description": "Error in HTTP connection."
|
||||
}
|
||||
HTTPClient.STATUS_TLS_HANDSHAKE_ERROR: return {
|
||||
"name": "STATUS_TLS_HANDSHAKE_ERROR",
|
||||
"description": "Error in TLS handshake."
|
||||
}
|
||||
return {
|
||||
"name": "UNKNOWN (%d)" % status,
|
||||
"description": "Unknown HTTPClient status."
|
||||
}
|
||||
|
||||
|
||||
func _bzz_httpclient_status_string(status: int) -> String:
|
||||
var info := _bzz_httpclient_status_info(status)
|
||||
return "%s (%s)" % [info["description"], info["name"]]
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
extends {{>partials/api_config_parent_class}}
|
||||
class_name {{>partials/api_config_class_name}}
|
||||
|
||||
{{>partials/disclaimer_autogenerated}}
|
||||
|
||||
# Configuration options for Api endpoints
|
||||
# =======================================
|
||||
#
|
||||
# Helps share configuration customizations across Apis:
|
||||
# - host, port & scheme
|
||||
# - extra headers (low priority, high priority)
|
||||
# - transport layer security options (TLS certificates)
|
||||
# - log level
|
||||
#
|
||||
# You probably want to make an instance of this class with your own values,
|
||||
# and feed it to each Api's constructor, before calling the Api's methods.
|
||||
#
|
||||
# Since it is a Resource, you may use `ResourceSaver.save()` and `preload()`
|
||||
# to save it and load it from file, for convenience.
|
||||
#
|
||||
|
||||
|
||||
# These are constant, immutable default values. Best not edit them.
|
||||
# To set different values at runtime, use the @export'ed properties below.
|
||||
const BEE_DEFAULT_HOST := "{{#if host}}{{{host}}}{{else}}localhost{{/if}}"
|
||||
const BEE_DEFAULT_PORT_HTTP := 80
|
||||
const BEE_DEFAULT_PORT_HTTPS := 443
|
||||
const BEE_DEFAULT_POLLING_INTERVAL_MS := 333 # milliseconds
|
||||
|
||||
|
||||
# Configuration also handles logging because it's convenient.
|
||||
enum LogLevel {
|
||||
SILENT,
|
||||
ERROR,
|
||||
WARNING,
|
||||
INFO,
|
||||
DEBUG,
|
||||
}
|
||||
|
||||
|
||||
## Log level to configure verbosity.
|
||||
@export var log_level := LogLevel.WARNING
|
||||
|
||||
{{!--
|
||||
# Not sure if this should hold the HTTPClient instance or not. Not for now.
|
||||
# Godot recommends using a single client for all requests, so it perhaps should.
|
||||
|
||||
# Godot's HTTP Client we are using.
|
||||
# If none was set (by you), we'll lazily make one.
|
||||
var bee_client: HTTPClient:
|
||||
set(value):
|
||||
bee_client = value
|
||||
get:
|
||||
if not bee_client:
|
||||
bee_client = HTTPClient.new()
|
||||
return bee_client
|
||||
--}}
|
||||
|
||||
## The host to connect to, with or without the protocol scheme.
|
||||
## Eg: "gitea.com", "https://gitea.com"
|
||||
## We toggle TLS accordingly to the provided scheme, if any.
|
||||
@export var host := BEE_DEFAULT_HOST:
|
||||
set(value):
|
||||
if value.begins_with("https://"):
|
||||
tls_enabled = true
|
||||
value = value.substr(8) # "https://".length() == 8
|
||||
elif value.begins_with("http://"):
|
||||
tls_enabled = false
|
||||
value = value.substr(7) # "http://".length() == 7
|
||||
host = value
|
||||
|
||||
|
||||
## Port through which the connection will be established.
|
||||
## NOTE: changing the host may change the port as well if the scheme was provided, see above.
|
||||
@export var port := BEE_DEFAULT_PORT_HTTP
|
||||
|
||||
|
||||
## Headers used as base for all requests made by Api instances using this config.
|
||||
## Those are the lowest priority headers, and are merged with custom headers provided in the bee_request() method call
|
||||
## as well as the headers override below, to compute the final, actually sent headers.
|
||||
@export var headers_base := {
|
||||
# Stigmergy: everyone does what is left to do (like ants do)
|
||||
"User-Agent": "Stigmergiac/1.0 (Godot)",
|
||||
# For my mental health's sake, only JSON is supported for now
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
|
||||
## High-priority headers, they will always override other headers coming from the base above or the method call.
|
||||
@export var headers_override := {}
|
||||
|
||||
|
||||
## Duration of sleep between poll() calls.
|
||||
@export var polling_interval_ms := BEE_DEFAULT_POLLING_INTERVAL_MS # milliseconds
|
||||
|
||||
|
||||
## Enable the Transport Security Layer (packet encryption, HTTPS)
|
||||
@export var tls_enabled := false:
|
||||
set(value):
|
||||
tls_enabled = value
|
||||
port = BEE_DEFAULT_PORT_HTTPS if tls_enabled else BEE_DEFAULT_PORT_HTTP
|
||||
|
||||
|
||||
## You should preload your *.crt file (the whole chain) in here if you want TLS.
|
||||
## I usually concatenate my /etc/ssl/certs/ca-certificates.crt and webserver certs here.
|
||||
## Remember to add the *.crt file to the exports, if necessary.
|
||||
@export var trusted_chain: X509Certificate # only used if tls is enabled
|
||||
@export var common_name_override := "" # for TLSOptions
|
||||
|
||||
|
||||
## Dynamic accessor using the TLS properties above, but you may inject your own
|
||||
## for example if you need to use TLSOptions.client_unsafe. Best not @export this.
|
||||
var tls_options: TLSOptions:
|
||||
set(value):
|
||||
tls_options = value
|
||||
get:
|
||||
if not tls_enabled:
|
||||
return null
|
||||
if not tls_options:
|
||||
tls_options = TLSOptions.client(trusted_chain, common_name_override)
|
||||
return tls_options
|
||||
|
||||
|
||||
func log_error(message: String):
|
||||
if self.log_level >= LogLevel.ERROR:
|
||||
push_error(message)
|
||||
|
||||
func log_warning(message: String):
|
||||
if self.log_level >= LogLevel.WARNING:
|
||||
push_warning(message)
|
||||
|
||||
func log_info(message: String):
|
||||
if self.log_level >= LogLevel.INFO:
|
||||
print(message)
|
||||
|
||||
func log_debug(message: String):
|
||||
if self.log_level >= LogLevel.DEBUG:
|
||||
print(message)
|
||||
|
||||
|
||||
{{#each authMethods}}
|
||||
# Authentication method `{{name}}`.
|
||||
{{#if isBasicBearer }}
|
||||
# Basic Bearer Authentication `{{bearerFormat}}`
|
||||
func set_security_{{name}}(value: String):
|
||||
self.headers_base["Authorization"] = "Bearer %s" % value
|
||||
|
||||
|
||||
{{else if isApiKey }}
|
||||
# Api Key Authentication `{{keyParamName}}`
|
||||
func set_security_{{name}}(value: String):
|
||||
{{#if isKeyInHeader }}
|
||||
self.headers_base["{{keyParamName}}"] = value
|
||||
{{else if isKeyInQuery }}
|
||||
# Implementing this should be straightforward
|
||||
log_error("Api Key in Query is not supported at the moment. (contribs welcome)")
|
||||
{{else if isKeyInCookie }}
|
||||
log_error("Api Key in Cookie is not supported at the moment. (contribs welcome)")
|
||||
{{else }}
|
||||
log_error("Unrecognized Api Key format (contribs welcome).")
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{else}}
|
||||
# → Skipped: not implemented in the gdscript templates. (contribs are welcome)
|
||||
|
||||
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
@@ -0,0 +1,41 @@
|
||||
extends {{>partials/api_error_parent_class}}
|
||||
class_name {{>partials/api_error_class_name}}
|
||||
|
||||
{{>partials/disclaimer_autogenerated}}
|
||||
|
||||
# Error wrapper provided to error callbacks
|
||||
# =========================================
|
||||
#
|
||||
# Whenever this OAS client fails to comply to your request, for any reason,
|
||||
# it will trigger the error callback, with an instance of this as parameter.
|
||||
#
|
||||
|
||||
## Helps finding the error in the code, among other things.
|
||||
## Could be a UUID, or even a translation key, so long as it's unique.
|
||||
## Right now we're mostly using a lowercase ~namespace joined by dots. (.)
|
||||
@export var identifier := ""
|
||||
|
||||
## A message for humans. May be multiline.
|
||||
@export var message := ""
|
||||
|
||||
## One of Godot's ERR_XXXX, when relevant.
|
||||
@export var internal_code := OK
|
||||
|
||||
## The HTTP response code, if any. (usually >= 400)
|
||||
## DEPRECATED: prefer reading from response object below
|
||||
@export var response_code := HTTPClient.RESPONSE_OK
|
||||
|
||||
## The HTTP response, if any.
|
||||
@export var response: {{>partials/api_response_class_name}}
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
var s := "{{>partials/api_error_class_name}}"
|
||||
if identifier:
|
||||
s += " %s" % identifier
|
||||
if message:
|
||||
s += " %s" % message
|
||||
if response:
|
||||
s += "\n%s" % str(response)
|
||||
return s
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
extends {{>partials/api_response_parent_class}}
|
||||
class_name {{>partials/api_response_class_name}}
|
||||
|
||||
{{>partials/disclaimer_autogenerated}}
|
||||
|
||||
# Response wrapper provided to success callbacks
|
||||
# ==============================================
|
||||
#
|
||||
# Holds the response metadata, its body, and the deserialized model(s), if any.
|
||||
# This object is directly passed to the success callback, and in case of failure
|
||||
# is injected in the error object.
|
||||
#
|
||||
|
||||
## Headers sent back by the server
|
||||
@export var headers := Dictionary()
|
||||
|
||||
## The HTTP response code, if any. A constant like HTTPClient.RESPONSE_XXXX
|
||||
@export var code := 0
|
||||
|
||||
## Raw body of this response, in String form (before deserialization)
|
||||
@export var body := ""
|
||||
|
||||
## Deserialized body (may be pretty much any type)
|
||||
var data
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
var s := "{{>partials/api_response_class_name}}"
|
||||
if code:
|
||||
s += " %d" % code
|
||||
if body:
|
||||
s += " %s" % body
|
||||
return s
|
||||
@@ -0,0 +1,121 @@
|
||||
{{#each models}}
|
||||
{{#with model}}
|
||||
{{>partials/model_statement_extends}}
|
||||
{{>partials/model_statement_class_name}}
|
||||
|
||||
|
||||
{{>partials/model_headers}}
|
||||
|
||||
|
||||
{{#each vars}}
|
||||
{{#if deprecated}}
|
||||
# /!. DEPRECATED
|
||||
{{/if}}
|
||||
{{#if description}}
|
||||
{{! FIXME: multiline description (how?) }}
|
||||
# {{{description}}}
|
||||
{{/if}}
|
||||
{{#if isDate}}
|
||||
# (but it's actually a Date ; no timezones support in Gdscript)
|
||||
{{/if}}
|
||||
{{#if isDateTime}}
|
||||
# (but it's actually a DateTime ; no timezones support in Gdscript)
|
||||
{{/if}}
|
||||
# Required: {{#unless required}}False{{/unless}}{{#if required}}True{{/if}}
|
||||
{{#if example}}
|
||||
# Example: {{{example}}}
|
||||
{{/if}}
|
||||
# isArray: {{isArray}}
|
||||
{{#if isEnum}}
|
||||
# Allowed values: {{#with allowableValues}}{{#each values}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}{{/with}}
|
||||
{{/if}}
|
||||
@export var {{name}}: {{>partials/data_type}}{{#if defaultValue}} = {{{defaultValue}}}{{/if}}:
|
||||
set(value):
|
||||
{{#if deprecated}}
|
||||
if str(value) != "":
|
||||
push_warning("{{classname}}: property `{{name}}` is deprecated.")
|
||||
{{/if}}
|
||||
{{#if isEnum}}
|
||||
if str(value) != "" and not (str(value) in __{{name}}__allowable__values):
|
||||
push_error("{{classname}}: tried to set property `{{name}}` to a value that is not allowed." +
|
||||
" Allowed values: {{#with allowableValues}}{{#each values}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}{{/with}}")
|
||||
return
|
||||
{{/if}}
|
||||
__{{name}}__was__set = true
|
||||
{{name}} = value
|
||||
{{! Flag used to only serialize what has been explicitely set. (no nullable types, anyway null might be legit) }}
|
||||
var __{{name}}__was__set := false
|
||||
{{! Store the allowed values if the property is an enum }}
|
||||
{{#if isEnum}}
|
||||
var __{{name}}__allowable__values := [
|
||||
{{~#with allowableValues}}{{#each values}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}{{~/with~}}
|
||||
]
|
||||
{{/if}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
func bzz_collect_missing_properties() -> Array:
|
||||
var bzz_missing_properties := Array()
|
||||
{{#each vars}}
|
||||
{{#if required}}
|
||||
if not self.__{{name}}__was__set:
|
||||
bzz_missing_properties.append("{{name}}")
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
return bzz_missing_properties
|
||||
|
||||
|
||||
func bzz_normalize() -> Dictionary:
|
||||
var bzz_dictionary := Dictionary()
|
||||
{{#each vars}}
|
||||
if self.__{{name}}__was__set:
|
||||
bzz_dictionary["{{name}}"] = self.{{name}}
|
||||
{{/each}}
|
||||
return bzz_dictionary
|
||||
|
||||
|
||||
# Won't work for JSON+LD
|
||||
{{!-- LEAKING if we specify return -> {{classname}} in func def --}}
|
||||
static func bzz_denormalize_single(from_dict: Dictionary):
|
||||
var me := new()
|
||||
{{#each vars}}
|
||||
if from_dict.has("{{name}}"):
|
||||
{{#if isModel}}
|
||||
me.{{name}} = {{>partials/complex_type}}.bzz_denormalize_single(from_dict["{{name}}"])
|
||||
{{else if isArray}}
|
||||
{{#if mostInnerItems.isModel}}
|
||||
me.{{name}} = {{>partials/complex_type}}.bzz_denormalize_multiple(from_dict["{{name}}"])
|
||||
{{else}}
|
||||
me.{{name}} = from_dict["{{name}}"]
|
||||
{{/if}}
|
||||
{{else}}
|
||||
me.{{name}} = from_dict["{{name}}"]
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
return me
|
||||
|
||||
|
||||
# Won't work for JSON+LD
|
||||
{{!-- LEAKING if we specify return -> {{classname}} in func def --}}
|
||||
static func bzz_denormalize_multiple(from_array: Array):
|
||||
var mes := Array()
|
||||
for element in from_array:
|
||||
if element is Array:
|
||||
mes.append(bzz_denormalize_multiple(element))
|
||||
elif element is Dictionary:
|
||||
# TODO: perhaps check first if it looks like a match or an intermediate container
|
||||
mes.append(bzz_denormalize_single(element))
|
||||
else:
|
||||
mes.append(element)
|
||||
return mes
|
||||
|
||||
{{!-- UNUSED
|
||||
func bzz_normalize_fully() -> Dictionary:
|
||||
return {
|
||||
{{#each vars}}
|
||||
"{{name}}": self.{{name}},
|
||||
{{/each}}
|
||||
}
|
||||
--}}
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
@@ -0,0 +1,10 @@
|
||||
**Partials** are bits of code that we reuse through templates,
|
||||
or use once but provide anyway in order to make template customization easier.
|
||||
|
||||
For example, if you only want to change the utmost parent class of API classes,
|
||||
you may override `api_bee_parent_class.handlebars` in this directory.
|
||||
|
||||
Note that these are probably not registered as actual partials to Handlebars,
|
||||
so they are _pseuds-partials_ and won't allow recursion.
|
||||
|
||||
> _May the fork be with you, always._
|
||||
@@ -0,0 +1 @@
|
||||
{{coreNamePrefix}}ApiBee{{coreNameSuffix}}
|
||||
@@ -0,0 +1,8 @@
|
||||
{{!--
|
||||
Override this template to let Apis inherit from your own desired class.
|
||||
You can for example use Node here of you want to use Apis as Autoloads,
|
||||
or provide the class_name of your own custom class, or even a quoted filepath.
|
||||
Anything that goes after the `extends` statement will work.
|
||||
TODO: would be nice to be able to customize this parent class from CLI.
|
||||
--}}
|
||||
RefCounted
|
||||
@@ -0,0 +1 @@
|
||||
{{coreNamePrefix}}ApiConfig{{coreNameSuffix}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{!--
|
||||
Override this template to let config inherit from your own desired class.
|
||||
It's probably best to use a descendant of Resource here (@export is used).
|
||||
TODO: would be neat to be able to customize this parent class from CLI.
|
||||
--}}
|
||||
Resource
|
||||
@@ -0,0 +1 @@
|
||||
{{coreNamePrefix}}ApiError{{coreNameSuffix}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{!--
|
||||
Override this template to let error inherit from your own desired class.
|
||||
It's probably best to use a descendant of Resource here (@export is used).
|
||||
TODO: would be cool to be able to customize this parent class from CLI.
|
||||
--}}
|
||||
Resource
|
||||
@@ -0,0 +1,4 @@
|
||||
{{>partials/disclaimer_autogenerated}}
|
||||
|
||||
# API {{classname}}
|
||||
# Instantiate this object and use it to make requests to the API.
|
||||
@@ -0,0 +1,9 @@
|
||||
{{#each allParams}}
|
||||
# {{paramName}}{{#if dataType}}: {{dataType}}{{/if}}{{#if defaultValue}} = {{{defaultValue}}}{{/if}}{{#if example}} Eg: {{{example}}}{{/if}}
|
||||
{{~#if description}}
|
||||
|
||||
# {{{description}}}{{/if}}
|
||||
{{paramName}}{{#if required}}{{#if dataType}}: {{{dataType}}}{{/if}}{{/if}}{{#unless required}} = {{#if defaultValue}}{{{defaultValue}}}{{/if}}{{#unless defaultValue}}null{{/unless}}{{/unless}},
|
||||
{{/each}}
|
||||
on_success: Callable = Callable(), # func(response: {{>partials/api_response_class_name}})
|
||||
on_failure: Callable = Callable(), # func(error: {{>partials/api_error_class_name}})
|
||||
@@ -0,0 +1 @@
|
||||
{{coreNamePrefix}}ApiResponse{{coreNameSuffix}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{!--
|
||||
Override this template to let response inherit from your own desired class.
|
||||
It's probably best to use a descendant of Resource here (@export is used).
|
||||
TODO: would be cool to be able to customize this parent class from CLI.
|
||||
--}}
|
||||
Resource
|
||||
@@ -0,0 +1 @@
|
||||
class {{classname}}
|
||||
@@ -0,0 +1 @@
|
||||
{{>partials/statement_class_name}}
|
||||
@@ -0,0 +1 @@
|
||||
extends {{>partials/api_base_class_name}}
|
||||
@@ -0,0 +1,2 @@
|
||||
{{! Hotfix to ensure our complex types include the "namespace" ~}}
|
||||
{{modelNamePrefix}}{{complexType}}{{modelNameSuffix~}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{! Hotfix to ensure our data types include the "namespace" ~}}
|
||||
{{#if isModel}}{{modelNamePrefix}}{{{dataType}}}{{modelNameSuffix}}{{/if~}}
|
||||
{{#unless isModel}}{{{dataType}}}{{/unless~}}
|
||||
@@ -0,0 +1,5 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED by the OpenAPI Generator project.
|
||||
# For more information on how to customize templates, see:
|
||||
# https://openapi-generator.tech
|
||||
# https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/gdscript
|
||||
# The OpenAPI Generator Community, © Public Domain, 2022
|
||||
@@ -0,0 +1,7 @@
|
||||
{{>partials/disclaimer_autogenerated}}
|
||||
|
||||
# {{classname}} Model
|
||||
{{#if description}}
|
||||
{{! FIXME: multiline description (how?) }}
|
||||
# {{{description}}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1 @@
|
||||
{{>partials/statement_class_name}}
|
||||
@@ -0,0 +1 @@
|
||||
extends Resource
|
||||
@@ -0,0 +1 @@
|
||||
class_name {{classname}}
|
||||
@@ -0,0 +1,5 @@
|
||||
Utils are not part of the template.
|
||||
No need to customize them.
|
||||
|
||||
They probably ought not be here, since this whole dir may be downloaded by generator consumers.
|
||||
Help me find the right spot for these !
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/bin/env python
|
||||
|
||||
# Generates and prints a list of reserved words in Godot
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import requests
|
||||
|
||||
|
||||
words_code = "" # output
|
||||
indentation = " "
|
||||
max_line_len = 119
|
||||
url = "https://raw.githubusercontent.com/godotengine/godot/master/doc/classes/%40GlobalScope.xml"
|
||||
|
||||
xml_string = requests.get(url).content
|
||||
root = ET.fromstring(xml_string)
|
||||
|
||||
methods_list = [] # of string
|
||||
constants_list = [] # of string
|
||||
singletons_list = [] # of string
|
||||
|
||||
for method in root.iter('method'):
|
||||
methods_list.append(method.attrib['name'])
|
||||
for constant in root.iter('constant'):
|
||||
constants_list.append(constant.attrib['name'])
|
||||
for singleton in root.iter('member'):
|
||||
singletons_list.append(singleton.attrib['name'])
|
||||
|
||||
words_code += "%s// List generated from modules/openapi-generator/src/main/resources/gdscript/utils/extract_reserved_words.py\n" % indentation
|
||||
|
||||
current_line = ""
|
||||
|
||||
def new_line():
|
||||
global current_line
|
||||
current_line = "%s" % indentation
|
||||
|
||||
def write_line():
|
||||
global current_line, words_code
|
||||
words_code += "%s\n" % current_line
|
||||
|
||||
def add_word(word):
|
||||
global current_line, words_code
|
||||
words_string = "\"%s\", " % word
|
||||
if len(current_line) + len(words_string) > max_line_len:
|
||||
write_line()
|
||||
new_line()
|
||||
current_line += words_string
|
||||
|
||||
new_line()
|
||||
|
||||
words_code += "%s// Godot's global functions\n" % indentation
|
||||
for reserved in methods_list:
|
||||
add_word(reserved)
|
||||
words_code += "%s// Godot's global constants\n" % indentation
|
||||
for reserved in constants_list:
|
||||
add_word(reserved)
|
||||
words_code += "%s// Godot's singletons\n" % indentation
|
||||
for reserved in singletons_list:
|
||||
add_word(reserved)
|
||||
|
||||
write_line()
|
||||
|
||||
print(words_code)
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.openapitools.codegen.gdscript;
|
||||
|
||||
import org.openapitools.codegen.*;
|
||||
import org.openapitools.codegen.languages.GdscriptClientCodegen;
|
||||
import io.swagger.models.*;
|
||||
import io.swagger.models.properties.*;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@SuppressWarnings("static-method")
|
||||
public class GdscriptClientCodegenModelTest {
|
||||
|
||||
@Test(description = "convert a simple java model")
|
||||
public void simpleModelTest() {
|
||||
final Model model = new ModelImpl()
|
||||
.description("a sample model")
|
||||
.property("id", new LongProperty())
|
||||
.property("name", new StringProperty())
|
||||
.required("id")
|
||||
.required("name");
|
||||
final DefaultCodegen codegen = new GdscriptClientCodegen();
|
||||
|
||||
// TODO: Complete this test.
|
||||
//Assert.fail("Not implemented.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.openapitools.codegen.gdscript;
|
||||
|
||||
import org.openapitools.codegen.languages.GdscriptClientCodegen;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class GdscriptClientCodegenTest {
|
||||
|
||||
GdscriptClientCodegen clientCodegen = new GdscriptClientCodegen();
|
||||
|
||||
@Test
|
||||
public void shouldSucceed() throws Exception {
|
||||
// TODO: Complete this test.
|
||||
//Assert.fail("Not implemented.");
|
||||
// verify(clientCodegen).setArtifactVersion(GdscriptClientCodegenOptionsProvider.ARTIFACT_VERSION_VALUE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.openapitools.codegen.gdscript;
|
||||
|
||||
//import org.openapitools.codegen.AbstractOptionsTest;
|
||||
//import org.openapitools.codegen.CodegenConfig;
|
||||
//import org.openapitools.codegen.languages.GdscriptClientCodegen;
|
||||
//import org.openapitools.codegen.options.GdscriptClientOptionsProvider;
|
||||
//
|
||||
//import static org.mockito.Mockito.mock;
|
||||
//import static org.mockito.Mockito.verify;
|
||||
|
||||
|
||||
// NOTE:
|
||||
// This is commented out because it fails to use handlebars.
|
||||
// It is perhaps trivial to fix, but I could not figure it out.
|
||||
// Anyway, there's no test case in here right now, so it does not matter much.
|
||||
|
||||
|
||||
//public class GdscriptClientOptionsTest extends AbstractOptionsTest {
|
||||
// private final GdscriptClientCodegen codegen = mock(GdscriptClientCodegen.class, mockSettings);
|
||||
//
|
||||
// public GdscriptClientOptionsTest() {
|
||||
// super(new GdscriptClientOptionsProvider());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected CodegenConfig getCodegenConfig() {
|
||||
// return codegen;
|
||||
// }
|
||||
//
|
||||
// @SuppressWarnings("unused")
|
||||
// @Override
|
||||
// protected void verifyOptions() {
|
||||
// // TODO: Complete options using Mockito
|
||||
// // verify(codegen).someMethod(arguments)
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.openapitools.codegen.options;
|
||||
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.languages.GdscriptClientCodegen;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class GdscriptClientOptionsProvider implements OptionsProvider {
|
||||
|
||||
// public static final String MODEL_PACKAGE_VALUE = "package";
|
||||
// public static final String API_PACKAGE_VALUE = "apiPackage";
|
||||
// public static final String VARIABLE_NAMING_CONVENTION_VALUE = "snake_case";
|
||||
// public static final String INVOKER_PACKAGE_VALUE = "OpenAPITools\\Client\\Php";
|
||||
// public static final String PACKAGE_NAME_VALUE = "OpenAPIToolsClient-php";
|
||||
// public static final String SRC_BASE_PATH_VALUE = "libPhp";
|
||||
// public static final String ARTIFACT_VERSION_VALUE = "1.0.0-SNAPSHOT";
|
||||
|
||||
public static final String SORT_PARAMS_VALUE = "false";
|
||||
public static final String SORT_MODEL_PROPERTIES_VALUE = "false";
|
||||
public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true";
|
||||
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
|
||||
public static final String PREPEND_FORM_OR_BODY_PARAMETERS_VALUE = "true";
|
||||
public static final String LEGACY_DISCRIMINATOR_BEHAVIOR_VALUE = "true";
|
||||
public static final String NO_ADDITIONAL_PROPERTIES_VALUE = "true";
|
||||
public static final String ENUM_UNKNOWN_DEFAULT_CASE_VALUE = "false";
|
||||
|
||||
@Override
|
||||
public String getLanguage() {
|
||||
return "gdscript";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> createOptions() {
|
||||
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<String, String>();
|
||||
return builder
|
||||
|
||||
.put(GdscriptClientCodegen.CORE_NAME_PREFIX, GdscriptClientCodegen.CORE_NAME_PREFIX_VALUE)
|
||||
.put(GdscriptClientCodegen.CORE_NAME_SUFFIX, GdscriptClientCodegen.CORE_NAME_SUFFIX_VALUE)
|
||||
.put(GdscriptClientCodegen.ANTICOLLISION_PREFIX, GdscriptClientCodegen.ANTICOLLISION_PREFIX_VALUE)
|
||||
.put(GdscriptClientCodegen.ANTICOLLISION_SUFFIX, GdscriptClientCodegen.ANTICOLLISION_SUFFIX_VALUE)
|
||||
|
||||
// Things we *might* need (we'll see)
|
||||
|
||||
// .put(CodegenConstants.API_PACKAGE, API_PACKAGE_VALUE)
|
||||
// .put(PhpClientCodegen.VARIABLE_NAMING_CONVENTION, VARIABLE_NAMING_CONVENTION_VALUE)
|
||||
// .put(CodegenConstants.INVOKER_PACKAGE, INVOKER_PACKAGE_VALUE)
|
||||
// .put(PhpClientCodegen.PACKAGE_NAME, PACKAGE_NAME_VALUE)
|
||||
// .put(PhpClientCodegen.SRC_BASE_PATH, SRC_BASE_PATH_VALUE)
|
||||
// .put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE)
|
||||
// .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true")
|
||||
|
||||
// The following is required by CodeGen
|
||||
|
||||
//.put(CodegenConstants.TEMPLATING_ENGINE, "handlebars")
|
||||
.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE)
|
||||
.put(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG, SORT_MODEL_PROPERTIES_VALUE)
|
||||
.put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE)
|
||||
.put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE)
|
||||
.put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE)
|
||||
.put(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, LEGACY_DISCRIMINATOR_BEHAVIOR_VALUE)
|
||||
.put(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, NO_ADDITIONAL_PROPERTIES_VALUE)
|
||||
.put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, ENUM_UNKNOWN_DEFAULT_CASE_VALUE)
|
||||
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServer() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,915 @@
|
||||
openapi: 3.0.0
|
||||
servers:
|
||||
- url: 'http://petstore.swagger.io/v2'
|
||||
info:
|
||||
description: >-
|
||||
This is a sample server Petstore server. For this sample, you can use the api key
|
||||
`special-key` to test the authorization filters.
|
||||
version: 1.0.0
|
||||
title: OpenAPI Petstore
|
||||
license:
|
||||
name: Apache-2.0
|
||||
url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
|
||||
tags:
|
||||
- name: pet
|
||||
description: Everything about your Pets
|
||||
- name: store
|
||||
description: Access to Petstore orders
|
||||
- name: user
|
||||
description: Operations about user
|
||||
paths:
|
||||
/pet:
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: Add a new pet to the store
|
||||
description: ''
|
||||
operationId: addPet
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'405':
|
||||
description: Invalid input
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Pet'
|
||||
put:
|
||||
tags:
|
||||
- pet
|
||||
summary: Update an existing pet
|
||||
description: ''
|
||||
operationId: updatePet
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Pet not found
|
||||
'405':
|
||||
description: Validation exception
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Pet'
|
||||
/pet/findByStatus:
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Finds Pets by status
|
||||
description: Multiple status values can be provided with comma separated strings
|
||||
operationId: findPetsByStatus
|
||||
parameters:
|
||||
- name: status
|
||||
in: query
|
||||
description: Status values that need to be considered for filter
|
||||
required: true
|
||||
style: form
|
||||
explode: false
|
||||
deprecated: true
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
default: available
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid status value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'read:pets'
|
||||
/pet/findByTags:
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Finds Pets by tags
|
||||
description: >-
|
||||
Multiple tags can be provided with comma separated strings. Use tag1,
|
||||
tag2, tag3 for testing.
|
||||
operationId: findPetsByTags
|
||||
parameters:
|
||||
- name: tags
|
||||
in: query
|
||||
description: Tags to filter by
|
||||
required: true
|
||||
style: form
|
||||
explode: false
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid tag value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'read:pets'
|
||||
deprecated: true
|
||||
'/pet/{petId}':
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Find pet by ID
|
||||
description: Returns a single pet
|
||||
operationId: getPetById
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet to return
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Pet not found
|
||||
security:
|
||||
- api_key: []
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: Updates a pet in the store with form data
|
||||
description: ''
|
||||
operationId: updatePetWithForm
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet that needs to be updated
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'405':
|
||||
description: Invalid input
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
requestBody:
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Updated name of the pet
|
||||
type: string
|
||||
status:
|
||||
description: Updated status of the pet
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- pet
|
||||
summary: Deletes a pet
|
||||
description: ''
|
||||
operationId: deletePet
|
||||
parameters:
|
||||
- name: api_key
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: petId
|
||||
in: path
|
||||
description: Pet id to delete
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid pet value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
'/pet/{petId}/uploadImage':
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: uploads an image
|
||||
description: ''
|
||||
operationId: uploadFile
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet to update
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: Additional data to pass to server
|
||||
type: string
|
||||
file:
|
||||
description: file to upload
|
||||
type: string
|
||||
format: binary
|
||||
/store/inventory:
|
||||
get:
|
||||
tags:
|
||||
- store
|
||||
summary: Returns pet inventories by status
|
||||
description: Returns a map of status codes to quantities
|
||||
operationId: getInventory
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
format: int32
|
||||
security:
|
||||
- api_key: []
|
||||
/store/order:
|
||||
post:
|
||||
tags:
|
||||
- store
|
||||
summary: Place an order for a pet
|
||||
description: ''
|
||||
operationId: placeOrder
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
'400':
|
||||
description: Invalid Order
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
description: order placed for purchasing the pet
|
||||
required: true
|
||||
'/store/order/{orderId}':
|
||||
get:
|
||||
tags:
|
||||
- store
|
||||
summary: Find purchase order by ID
|
||||
description: >-
|
||||
For valid response try integer IDs with value <= 5 or > 10. Other values
|
||||
will generate exceptions
|
||||
operationId: getOrderById
|
||||
parameters:
|
||||
- name: orderId
|
||||
in: path
|
||||
description: ID of pet that needs to be fetched
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 1
|
||||
maximum: 5
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Order not found
|
||||
delete:
|
||||
tags:
|
||||
- store
|
||||
summary: Delete purchase order by ID
|
||||
description: >-
|
||||
For valid response try integer IDs with value < 1000. Anything above
|
||||
1000 or nonintegers will generate API errors
|
||||
operationId: deleteOrder
|
||||
parameters:
|
||||
- name: orderId
|
||||
in: path
|
||||
description: ID of the order that needs to be deleted
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Order not found
|
||||
/user:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Create user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: createUser
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
security:
|
||||
- api_key: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: Created user object
|
||||
required: true
|
||||
/user/createWithArray:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Creates list of users with given input array
|
||||
description: ''
|
||||
operationId: createUsersWithArrayInput
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
security:
|
||||
- api_key: []
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/UserArray'
|
||||
/user/createWithList:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Creates list of users with given input array
|
||||
description: ''
|
||||
operationId: createUsersWithListInput
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
security:
|
||||
- api_key: []
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/UserArray'
|
||||
/user/login:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Logs user into the system
|
||||
description: ''
|
||||
operationId: loginUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: query
|
||||
description: The user name for login
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$'
|
||||
- name: password
|
||||
in: query
|
||||
description: The password for login in clear text
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
headers:
|
||||
Set-Cookie:
|
||||
description: >-
|
||||
Cookie authentication key for use with the `api_key`
|
||||
apiKey authentication.
|
||||
schema:
|
||||
type: string
|
||||
example: AUTH_KEY=abcde12345; Path=/; HttpOnly
|
||||
X-Rate-Limit:
|
||||
description: calls per hour allowed by the user
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
X-Expires-After:
|
||||
description: date in UTC when token expires
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: string
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
'400':
|
||||
description: Invalid username/password supplied
|
||||
/user/logout:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Logs out current logged in user session
|
||||
description: ''
|
||||
operationId: logoutUser
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
security:
|
||||
- api_key: []
|
||||
'/user/{username}':
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Get user by user name
|
||||
description: ''
|
||||
operationId: getUserByName
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: The name that needs to be fetched. Use user1 for testing.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'400':
|
||||
description: Invalid username supplied
|
||||
'404':
|
||||
description: User not found
|
||||
put:
|
||||
tags:
|
||||
- user
|
||||
summary: Updated user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: updateUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: name that need to be deleted
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid user supplied
|
||||
'404':
|
||||
description: User not found
|
||||
security:
|
||||
- api_key: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: Updated user object
|
||||
required: true
|
||||
delete:
|
||||
tags:
|
||||
- user
|
||||
summary: Delete user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: deleteUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: The name that needs to be deleted
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid username supplied
|
||||
'404':
|
||||
description: User not found
|
||||
security:
|
||||
- api_key: []
|
||||
'/fake/user/{username}':
|
||||
get:
|
||||
tags:
|
||||
- fake
|
||||
summary: To test nullable required parameters
|
||||
description: ''
|
||||
operationId: test_nullable_required_param
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: The name that needs to be fetched. Use user1 for testing.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: dummy_required_nullable_param
|
||||
in: header
|
||||
description: To test nullable required parameters
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
- name: UPPERCASE
|
||||
in: header
|
||||
description: To test parameter names in upper case
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
'400':
|
||||
description: Invalid username supplied
|
||||
'404':
|
||||
description: User not found
|
||||
'/tests/fileResponse':
|
||||
get:
|
||||
tags:
|
||||
- testing
|
||||
summary: Returns an image file
|
||||
responses:
|
||||
'200':
|
||||
description: An image file
|
||||
content:
|
||||
image/jpeg:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
'/tests/typeTesting':
|
||||
get:
|
||||
tags:
|
||||
- testing
|
||||
summary: Route to test the TypeTesting schema
|
||||
responses:
|
||||
'200':
|
||||
description: The TypeTesting response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypeTesting'
|
||||
externalDocs:
|
||||
description: Find out more about Swagger
|
||||
url: 'http://swagger.io'
|
||||
components:
|
||||
requestBodies:
|
||||
UserArray:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: List of user object
|
||||
required: true
|
||||
Pet:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
description: Pet object that needs to be added to the store
|
||||
required: true
|
||||
securitySchemes:
|
||||
petstore_auth:
|
||||
type: oauth2
|
||||
flows:
|
||||
implicit:
|
||||
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
|
||||
scopes:
|
||||
'write:pets': modify pets in your account
|
||||
'read:pets': read your pets
|
||||
api_key:
|
||||
type: apiKey
|
||||
name: api_key
|
||||
in: header
|
||||
schemas:
|
||||
Order:
|
||||
title: Pet Order
|
||||
description: An order for a pets from the pet store
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
petId:
|
||||
type: integer
|
||||
format: int64
|
||||
quantity:
|
||||
type: integer
|
||||
format: int32
|
||||
shipDate:
|
||||
type: string
|
||||
format: date-time
|
||||
status:
|
||||
type: string
|
||||
description: Order Status
|
||||
enum:
|
||||
- placed
|
||||
- approved
|
||||
- delivered
|
||||
complete:
|
||||
type: boolean
|
||||
default: false
|
||||
xml:
|
||||
name: Order
|
||||
Category:
|
||||
title: Pet category
|
||||
description: A category for a pet
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$'
|
||||
xml:
|
||||
name: Category
|
||||
User:
|
||||
title: a User
|
||||
description: A User who is purchasing from the pet store
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
username:
|
||||
type: string
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
userStatus:
|
||||
type: integer
|
||||
format: int32
|
||||
description: User Status
|
||||
xml:
|
||||
name: User
|
||||
Tag:
|
||||
title: Pet Tag
|
||||
description: A tag for a pet
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
xml:
|
||||
name: Tag
|
||||
Pet:
|
||||
title: a Pet
|
||||
description: A pet for sale in the pet store
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- photoUrls
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
category:
|
||||
$ref: '#/components/schemas/Category'
|
||||
name:
|
||||
type: string
|
||||
example: doggie
|
||||
photoUrls:
|
||||
type: array
|
||||
xml:
|
||||
name: photoUrl
|
||||
wrapped: true
|
||||
items:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
xml:
|
||||
name: tag
|
||||
wrapped: true
|
||||
items:
|
||||
$ref: '#/components/schemas/Tag'
|
||||
status:
|
||||
type: string
|
||||
description: pet status in the store
|
||||
deprecated: true
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
xml:
|
||||
name: Pet
|
||||
ApiResponse:
|
||||
title: An uploaded response
|
||||
description: Describes the result of uploading an image resource
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
format: int32
|
||||
type:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
property_test:
|
||||
title: A model to test various formats, e.g. UUID
|
||||
description: A model to test various formats, e.g. UUID
|
||||
type: object
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
ActionContainer:
|
||||
required:
|
||||
- action
|
||||
type: object
|
||||
properties:
|
||||
action:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Baz'
|
||||
- nullable: false
|
||||
Baz:
|
||||
description: Test handling of empty variants
|
||||
enum:
|
||||
- A
|
||||
- B
|
||||
- ""
|
||||
type: string
|
||||
TypeTesting:
|
||||
description: Test handling of different field data types
|
||||
type: object
|
||||
required:
|
||||
- int32
|
||||
- int64
|
||||
- float
|
||||
- double
|
||||
- string
|
||||
- boolean
|
||||
- uuid
|
||||
properties:
|
||||
int32:
|
||||
type: integer
|
||||
format: int32
|
||||
int64:
|
||||
type: integer
|
||||
format: int64
|
||||
float:
|
||||
type: number
|
||||
format: float
|
||||
double:
|
||||
type: number
|
||||
format: double
|
||||
string:
|
||||
type: string
|
||||
boolean:
|
||||
type: boolean
|
||||
uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
Return:
|
||||
description: Test using keywords
|
||||
type: object
|
||||
properties:
|
||||
match:
|
||||
type: integer
|
||||
async:
|
||||
type: boolean
|
||||
super:
|
||||
type: boolean
|
||||
OptionalTesting:
|
||||
description: Test handling of optional and nullable fields
|
||||
type: object
|
||||
required:
|
||||
- required_nonnull
|
||||
- required_nullable
|
||||
properties:
|
||||
optional_nonnull:
|
||||
type: string
|
||||
nullable: false
|
||||
required_nonnull:
|
||||
type: string
|
||||
nullable: false
|
||||
optional_nullable:
|
||||
type: string
|
||||
nullable: true
|
||||
required_nullable:
|
||||
type: string
|
||||
nullable: true
|
||||
EnumArrayTesting:
|
||||
description: Test of enum array
|
||||
type: object
|
||||
required:
|
||||
- required_enums
|
||||
properties:
|
||||
required_enums:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: ["A", "B", "C"]
|
||||
ArrayRefItem:
|
||||
description: Helper object for the array item ref test
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
ObjectRefItem:
|
||||
description: Helper object for the array item ref test
|
||||
type: object
|
||||
additionalProperties: true
|
||||
ArrayItemRefTest:
|
||||
description: Test handling of object reference in arrays
|
||||
type: object
|
||||
required:
|
||||
- list_with_array_ref
|
||||
- list_with_object_ref
|
||||
properties:
|
||||
list_with_array_ref:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ArrayRefItem'
|
||||
list_with_object_ref:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ObjectRefItem'
|
||||
4
samples/client/petstore/gdscript/.gitignore
vendored
Normal file
4
samples/client/petstore/gdscript/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
|
||||
*.import
|
||||
42
samples/client/petstore/gdscript/.gut_editor_config.json
Normal file
42
samples/client/petstore/gdscript/.gut_editor_config.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"background_color": "262626ff",
|
||||
"compact_mode": false,
|
||||
"config_file": "res://.gutconfig.json",
|
||||
"dirs": [
|
||||
"res://test/integration"
|
||||
],
|
||||
"disable_colors": false,
|
||||
"double_strategy": "partial",
|
||||
"font_color": "ccccccff",
|
||||
"font_name": "CourierPrime",
|
||||
"font_size": 16,
|
||||
"gut_on_top": true,
|
||||
"hide_orphans": false,
|
||||
"ignore_pause": false,
|
||||
"include_subdirs": false,
|
||||
"inner_class": null,
|
||||
"junit_xml_file": "",
|
||||
"junit_xml_timestamp": false,
|
||||
"log_level": 1,
|
||||
"opacity": 100,
|
||||
"paint_after": 0.5,
|
||||
"panel_options": {
|
||||
"font_name": "CourierPrime",
|
||||
"font_size": 30,
|
||||
"hide_output_text": false,
|
||||
"hide_result_tree": false,
|
||||
"hide_settings": false,
|
||||
"use_colors": false
|
||||
},
|
||||
"post_run_script": "",
|
||||
"pre_run_script": "",
|
||||
"prefix": "test_",
|
||||
"selected": null,
|
||||
"should_exit": false,
|
||||
"should_exit_on_success": false,
|
||||
"should_maximize": false,
|
||||
"show_help": false,
|
||||
"suffix": ".gd",
|
||||
"tests": [],
|
||||
"unit_test_name": null
|
||||
}
|
||||
17
samples/client/petstore/gdscript/.gut_editor_shortcuts.cfg
Normal file
17
samples/client/petstore/gdscript/.gut_editor_shortcuts.cfg
Normal file
@@ -0,0 +1,17 @@
|
||||
[main]
|
||||
|
||||
run_all=Object(Shortcut,"resource_local_to_scene":false,"resource_name":"","events":[Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
],"script":null)
|
||||
|
||||
run_current_script=Object(Shortcut,"resource_local_to_scene":false,"resource_name":"","events":[Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
],"script":null)
|
||||
|
||||
run_current_inner=Object(Shortcut,"resource_local_to_scene":false,"resource_name":"","events":[Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
],"script":null)
|
||||
|
||||
run_current_test=Object(Shortcut,"resource_local_to_scene":false,"resource_name":"","events":[Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
],"script":null)
|
||||
|
||||
panel_button=Object(Shortcut,"resource_local_to_scene":false,"resource_name":"","events":[Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||
],"script":null)
|
||||
|
||||
32
samples/client/petstore/gdscript/.gutconfig.json
Normal file
32
samples/client/petstore/gdscript/.gutconfig.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"background_color": null,
|
||||
"compact_mode": null,
|
||||
"dirs": [
|
||||
"res://test/integration"
|
||||
],
|
||||
"disable_colors": null,
|
||||
"double_strategy": null,
|
||||
"font_color": null,
|
||||
"font_name": null,
|
||||
"font_size": null,
|
||||
"gut_on_top": null,
|
||||
"hide_orphans": null,
|
||||
"ignore_pause": null,
|
||||
"include_subdirs": null,
|
||||
"inner_class": null,
|
||||
"junit_xml_file": null,
|
||||
"junit_xml_timestamp": null,
|
||||
"log_level": null,
|
||||
"opacity": null,
|
||||
"paint_after": null,
|
||||
"post_run_script": null,
|
||||
"pre_run_script": null,
|
||||
"prefix": null,
|
||||
"selected": null,
|
||||
"should_exit": true,
|
||||
"should_exit_on_success": null,
|
||||
"should_maximize": null,
|
||||
"suffix": null,
|
||||
"tests": null,
|
||||
"unit_test_name": null
|
||||
}
|
||||
16
samples/client/petstore/gdscript/.openapi-generator/FILES
Normal file
16
samples/client/petstore/gdscript/.openapi-generator/FILES
Normal file
@@ -0,0 +1,16 @@
|
||||
README.md
|
||||
apis/DemoPetApi.gd
|
||||
apis/DemoPetApi.md
|
||||
apis/DemoStoreApi.gd
|
||||
apis/DemoStoreApi.md
|
||||
apis/DemoUserApi.gd
|
||||
apis/DemoUserApi.md
|
||||
core/DemoApiBee.gd
|
||||
core/DemoApiConfig.gd
|
||||
core/DemoApiError.gd
|
||||
models/DemoApiResponse.gd
|
||||
models/DemoCategory.gd
|
||||
models/DemoOrder.gd
|
||||
models/DemoPet.gd
|
||||
models/DemoTag.gd
|
||||
models/DemoUser.gd
|
||||
@@ -0,0 +1 @@
|
||||
6.2.1-SNAPSHOT
|
||||
36
samples/client/petstore/gdscript/README.md
Normal file
36
samples/client/petstore/gdscript/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Petstore GDScript Testing Client
|
||||
|
||||
## What
|
||||
|
||||
- [Godot] `4.x` project
|
||||
- Made for `--headless` mode
|
||||
- Returns non-zero exit code upon test failure
|
||||
- Uses [GUT] as test engine
|
||||
|
||||
|
||||
## Prepare the test server
|
||||
|
||||
See https://github.com/OpenAPITools/openapi-generator/wiki/Integration-Tests
|
||||
|
||||
> We are using the petstore docker, not the echo server for now.
|
||||
> Feel free to refactor or duplicate the sample demo to use the new echo server.
|
||||
> See `bin/configs/gdscript-petstore.yaml`.
|
||||
|
||||
|
||||
## Run
|
||||
|
||||
godot --headless --debug --path samples/client/petstore/gdscript --script addons/gut/gut_cmdln.gd
|
||||
|
||||
The command should return a _zero_ exit code if all tests _passed_.
|
||||
You may want to add `--verbose` for more logs when debugging.
|
||||
|
||||
|
||||
## Update
|
||||
|
||||
Refresh the generated files, after modifying the gdscript templates or java generator:
|
||||
|
||||
bin/generate-samples.sh bin/configs/gdscript-petstore.yaml
|
||||
|
||||
|
||||
[Godot]: https://godotengine.org
|
||||
[GUT]: https://github.com/bitwes/Gut
|
||||
124
samples/client/petstore/gdscript/addons/gut/GutScene.gd
Normal file
124
samples/client/petstore/gdscript/addons/gut/GutScene.gd
Normal file
@@ -0,0 +1,124 @@
|
||||
extends Node2D
|
||||
# ##############################################################################
|
||||
# This is a wrapper around the normal and compact gui controls and serves as
|
||||
# the interface between gut.gd and the gui. The GutRunner creates an instance
|
||||
# of this and then this takes care of managing the different GUI controls.
|
||||
# ##############################################################################
|
||||
@onready var _normal_gui = $Normal
|
||||
@onready var _compact_gui = $Compact
|
||||
var gut = null :
|
||||
set(val):
|
||||
gut = val
|
||||
_set_gut(val)
|
||||
|
||||
|
||||
func _ready():
|
||||
_normal_gui.switch_modes.connect(use_compact_mode.bind(true))
|
||||
_compact_gui.switch_modes.connect(use_compact_mode.bind(false))
|
||||
|
||||
_normal_gui.set_title("GUT")
|
||||
_compact_gui.set_title("GUT")
|
||||
|
||||
_normal_gui.align_right()
|
||||
_compact_gui.to_bottom_right()
|
||||
|
||||
use_compact_mode(false)
|
||||
|
||||
if(get_parent() == get_tree().root):
|
||||
_test_running_setup()
|
||||
|
||||
func _test_running_setup():
|
||||
_normal_gui.get_textbox().text = "hello world, how are you doing?"
|
||||
|
||||
# ------------------------
|
||||
# Private
|
||||
# ------------------------
|
||||
func _set_gut(val):
|
||||
_normal_gui.set_gut(val)
|
||||
_compact_gui.set_gut(val)
|
||||
|
||||
val.start_run.connect(_on_gut_start_run)
|
||||
val.end_run.connect(_on_gut_end_run)
|
||||
val.start_pause_before_teardown.connect(_on_gut_pause)
|
||||
val.end_pause_before_teardown.connect(_on_pause_end)
|
||||
|
||||
func _set_both_titles(text):
|
||||
_normal_gui.set_title(text)
|
||||
_compact_gui.set_title(text)
|
||||
|
||||
|
||||
# ------------------------
|
||||
# Events
|
||||
# ------------------------
|
||||
func _on_gut_start_run():
|
||||
_set_both_titles('Running')
|
||||
|
||||
func _on_gut_end_run():
|
||||
_set_both_titles('Finished')
|
||||
|
||||
func _on_gut_pause():
|
||||
_set_both_titles('-- Paused --')
|
||||
|
||||
func _on_pause_end():
|
||||
_set_both_titles('Running')
|
||||
|
||||
|
||||
# ------------------------
|
||||
# Public
|
||||
# ------------------------
|
||||
func get_textbox():
|
||||
return _normal_gui.get_textbox()
|
||||
|
||||
|
||||
func set_font_size(new_size):
|
||||
return
|
||||
var rtl = _normal_gui.get_textbox()
|
||||
if(rtl.get('custom_fonts/normal_font') != null):
|
||||
rtl.get('custom_fonts/bold_italics_font').size = new_size
|
||||
rtl.get('custom_fonts/bold_font').size = new_size
|
||||
rtl.get('custom_fonts/italics_font').size = new_size
|
||||
rtl.get('custom_fonts/normal_font').size = new_size
|
||||
|
||||
|
||||
func set_font(font_name):
|
||||
_set_all_fonts_in_rtl(_normal_gui.get_textbox(), font_name)
|
||||
|
||||
|
||||
func _set_font(rtl, font_name, custom_name):
|
||||
if(font_name == null):
|
||||
rtl.add_theme_font_override(custom_name, null)
|
||||
else:
|
||||
var dyn_font = FontFile.new()
|
||||
dyn_font.load_dynamic_font('res://addons/gut/fonts/' + font_name + '.ttf')
|
||||
rtl.add_theme_font_override(custom_name, dyn_font)
|
||||
|
||||
|
||||
func _set_all_fonts_in_rtl(rtl, base_name):
|
||||
if(base_name == 'Default'):
|
||||
_set_font(rtl, null, 'normal_font')
|
||||
_set_font(rtl, null, 'bold_font')
|
||||
_set_font(rtl, null, 'italics_font')
|
||||
_set_font(rtl, null, 'bold_italics_font')
|
||||
else:
|
||||
_set_font(rtl, base_name + '-Regular', 'normal_font')
|
||||
_set_font(rtl, base_name + '-Bold', 'bold_font')
|
||||
_set_font(rtl, base_name + '-Italic', 'italics_font')
|
||||
_set_font(rtl, base_name + '-BoldItalic', 'bold_italics_font')
|
||||
|
||||
|
||||
func set_default_font_color(color):
|
||||
_normal_gui.get_textbox().set('custom_colors/default_color', color)
|
||||
|
||||
|
||||
func set_background_color(color):
|
||||
_normal_gui.set_bg_color(color)
|
||||
|
||||
|
||||
func use_compact_mode(should=true):
|
||||
_compact_gui.visible = should
|
||||
_normal_gui.visible = !should
|
||||
|
||||
|
||||
func set_opacity(val):
|
||||
_normal_gui.modulate.a = val
|
||||
_compact_gui.modulate.a = val
|
||||
16
samples/client/petstore/gdscript/addons/gut/GutScene.tscn
Normal file
16
samples/client/petstore/gdscript/addons/gut/GutScene.tscn
Normal file
@@ -0,0 +1,16 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://m28heqtswbuq"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/GutScene.gd" id="1_b4m8y"]
|
||||
[ext_resource type="PackedScene" uid="uid://duxblir3vu8x7" path="res://addons/gut/gui/NormalGui.tscn" id="2_j6ywb"]
|
||||
[ext_resource type="PackedScene" uid="uid://cnqqdfsn80ise" path="res://addons/gut/gui/MinGui.tscn" id="3_3glw1"]
|
||||
|
||||
[node name="GutScene" type="Node2D"]
|
||||
script = ExtResource("1_b4m8y")
|
||||
|
||||
[node name="Normal" parent="." instance=ExtResource("2_j6ywb")]
|
||||
|
||||
[node name="Compact" parent="." instance=ExtResource("3_3glw1")]
|
||||
offset_left = 5.0
|
||||
offset_top = 273.0
|
||||
offset_right = 265.0
|
||||
offset_bottom = 403.0
|
||||
22
samples/client/petstore/gdscript/addons/gut/LICENSE.md
Normal file
22
samples/client/petstore/gdscript/addons/gut/LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright (c) 2018 Tom "Butch" Wesley
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,52 @@
|
||||
extends Window
|
||||
|
||||
@onready var rtl = $TextDisplay/RichTextLabel
|
||||
|
||||
func _get_file_as_text(path):
|
||||
var to_return = null
|
||||
var f = FileAccess.open(path, FileAccess.READ)
|
||||
if(f != null):
|
||||
to_return = f.get_as_text()
|
||||
else:
|
||||
to_return = str('ERROR: Could not open file. Error code ', FileAccess.get_open_error())
|
||||
return to_return
|
||||
|
||||
func _ready():
|
||||
rtl.clear()
|
||||
|
||||
func _on_OpenFile_pressed():
|
||||
$FileDialog.popup_centered()
|
||||
|
||||
func _on_FileDialog_file_selected(path):
|
||||
show_file(path)
|
||||
|
||||
func _on_Close_pressed():
|
||||
self.hide()
|
||||
|
||||
func show_file(path):
|
||||
var text = _get_file_as_text(path)
|
||||
if(text == ''):
|
||||
text = '<Empty File>'
|
||||
rtl.set_text(text)
|
||||
self.window_title = path
|
||||
|
||||
func show_open():
|
||||
self.popup_centered()
|
||||
$FileDialog.popup_centered()
|
||||
|
||||
func get_rich_text_label():
|
||||
return $TextDisplay/RichTextLabel
|
||||
|
||||
func _on_Home_pressed():
|
||||
rtl.scroll_to_line(0)
|
||||
|
||||
func _on_End_pressed():
|
||||
rtl.scroll_to_line(rtl.get_line_count() -1)
|
||||
|
||||
func _on_Copy_pressed():
|
||||
return
|
||||
# OS.clipboard = rtl.text
|
||||
|
||||
func _on_file_dialog_visibility_changed():
|
||||
if rtl.text.length() == 0 and not $FileDialog.visible:
|
||||
self.hide()
|
||||
@@ -0,0 +1,92 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bsm7wtt1gie4v"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/UserFileViewer.gd" id="1"]
|
||||
|
||||
[node name="UserFileViewer" type="Window"]
|
||||
exclusive = true
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="FileDialog" type="FileDialog" parent="."]
|
||||
access = 1
|
||||
show_hidden_files = true
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="TextDisplay" type="ColorRect" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_right = -10.0
|
||||
offset_bottom = -65.0
|
||||
color = Color(0.2, 0.188235, 0.188235, 1)
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="TextDisplay"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
focus_mode = 2
|
||||
text = "In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design.
|
||||
|
||||
Lorem ipsum is typically a corrupted version of De finibus bonorum et malorum, a first-century BCE text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical, improper Latin.
|
||||
|
||||
Versions of the Lorem ipsum text have been used in typesetting at least since the 1960s, when it was popularized by advertisements for Letraset transfer sheets. Lorem ipsum was introduced to the digital world in the mid-1980s when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors including Pages and Microsoft Word have since adopted Lorem ipsum as well."
|
||||
selection_enabled = true
|
||||
|
||||
[node name="OpenFile" type="Button" parent="."]
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -158.0
|
||||
offset_top = -50.0
|
||||
offset_right = -84.0
|
||||
offset_bottom = -30.0
|
||||
text = "Open File"
|
||||
|
||||
[node name="Home" type="Button" parent="."]
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -478.0
|
||||
offset_top = -50.0
|
||||
offset_right = -404.0
|
||||
offset_bottom = -30.0
|
||||
text = "Home"
|
||||
|
||||
[node name="Copy" type="Button" parent="."]
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 160.0
|
||||
offset_top = -50.0
|
||||
offset_right = 234.0
|
||||
offset_bottom = -30.0
|
||||
text = "Copy"
|
||||
|
||||
[node name="End" type="Button" parent="."]
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -318.0
|
||||
offset_top = -50.0
|
||||
offset_right = -244.0
|
||||
offset_bottom = -30.0
|
||||
text = "End"
|
||||
|
||||
[node name="Close" type="Button" parent="."]
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 10.0
|
||||
offset_top = -50.0
|
||||
offset_right = 80.0
|
||||
offset_bottom = -30.0
|
||||
text = "Close"
|
||||
|
||||
[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]
|
||||
[connection signal="visibility_changed" from="FileDialog" to="." method="_on_file_dialog_visibility_changed"]
|
||||
[connection signal="pressed" from="OpenFile" to="." method="_on_OpenFile_pressed"]
|
||||
[connection signal="pressed" from="Home" to="." method="_on_Home_pressed"]
|
||||
[connection signal="pressed" from="Copy" to="." method="_on_Copy_pressed"]
|
||||
[connection signal="pressed" from="End" to="." method="_on_End_pressed"]
|
||||
[connection signal="pressed" from="Close" to="." method="_on_Close_pressed"]
|
||||
59
samples/client/petstore/gdscript/addons/gut/autofree.gd
Normal file
59
samples/client/petstore/gdscript/addons/gut/autofree.gd
Normal file
@@ -0,0 +1,59 @@
|
||||
# ##############################################################################
|
||||
#(G)odot (U)nit (T)est class
|
||||
#
|
||||
# ##############################################################################
|
||||
# The MIT License (MIT)
|
||||
# =====================
|
||||
#
|
||||
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# ##############################################################################
|
||||
# Class used to keep track of objects to be freed and utilities to free them.
|
||||
# ##############################################################################
|
||||
var _to_free = []
|
||||
var _to_queue_free = []
|
||||
|
||||
func add_free(thing):
|
||||
if(typeof(thing) == TYPE_OBJECT):
|
||||
if(!thing is RefCounted):
|
||||
_to_free.append(thing)
|
||||
|
||||
func add_queue_free(thing):
|
||||
_to_queue_free.append(thing)
|
||||
|
||||
func get_queue_free_count():
|
||||
return _to_queue_free.size()
|
||||
|
||||
func get_free_count():
|
||||
return _to_free.size()
|
||||
|
||||
func free_all():
|
||||
for i in range(_to_free.size()):
|
||||
if(is_instance_valid(_to_free[i])):
|
||||
_to_free[i].free()
|
||||
_to_free.clear()
|
||||
|
||||
for i in range(_to_queue_free.size()):
|
||||
if(is_instance_valid(_to_queue_free[i])):
|
||||
_to_queue_free[i].queue_free()
|
||||
_to_queue_free.clear()
|
||||
|
||||
|
||||
67
samples/client/petstore/gdscript/addons/gut/awaiter.gd
Normal file
67
samples/client/petstore/gdscript/addons/gut/awaiter.gd
Normal file
@@ -0,0 +1,67 @@
|
||||
extends Node
|
||||
|
||||
signal timeout
|
||||
signal wait_started
|
||||
|
||||
var _wait_time = 0.0
|
||||
var _wait_frames = 0
|
||||
var _signal_to_wait_on = null
|
||||
|
||||
var _elapsed_time = 0.0
|
||||
var _elapsed_frames = 0
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
if(_wait_time != 0.0):
|
||||
_elapsed_time += delta
|
||||
if(_elapsed_time >= _wait_time):
|
||||
_end_wait()
|
||||
|
||||
if(_wait_frames != 0):
|
||||
_elapsed_frames += 1
|
||||
if(_elapsed_frames >= _wait_frames):
|
||||
_end_wait()
|
||||
|
||||
|
||||
func _end_wait():
|
||||
_wait_time = 0.0
|
||||
_wait_frames = 0
|
||||
_signal_to_wait_on = null
|
||||
_elapsed_time = 0.0
|
||||
_elapsed_frames = 0
|
||||
timeout.emit()
|
||||
|
||||
|
||||
const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_'
|
||||
func _signal_callback(
|
||||
arg1=ARG_NOT_SET, arg2=ARG_NOT_SET, arg3=ARG_NOT_SET,
|
||||
arg4=ARG_NOT_SET, arg5=ARG_NOT_SET, arg6=ARG_NOT_SET,
|
||||
arg7=ARG_NOT_SET, arg8=ARG_NOT_SET, arg9=ARG_NOT_SET):
|
||||
|
||||
_signal_to_wait_on.disconnect(_signal_callback)
|
||||
# DO NOT _end_wait here. For other parts of the test to get the signal that
|
||||
# was waited on, we have to wait for a couple more frames. For example, the
|
||||
# signal_watcher doesn't get the signal in time if we don't do this.
|
||||
_wait_frames = 2
|
||||
|
||||
|
||||
func wait_for(x):
|
||||
_wait_time = x
|
||||
wait_started.emit()
|
||||
|
||||
|
||||
func wait_frames(x):
|
||||
_wait_frames = x
|
||||
wait_started.emit()
|
||||
|
||||
|
||||
func wait_for_signal(the_signal, x):
|
||||
the_signal.connect(_signal_callback)
|
||||
_signal_to_wait_on = the_signal
|
||||
_wait_time = x
|
||||
wait_started.emit()
|
||||
|
||||
|
||||
func is_waiting():
|
||||
return _wait_time != 0.0 || _wait_frames != 0
|
||||
|
||||
123
samples/client/petstore/gdscript/addons/gut/comparator.gd
Normal file
123
samples/client/petstore/gdscript/addons/gut/comparator.gd
Normal file
@@ -0,0 +1,123 @@
|
||||
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||
var _strutils = _utils.Strutils.new()
|
||||
var _max_length = 100
|
||||
var _should_compare_int_to_float = true
|
||||
|
||||
const MISSING = '|__missing__gut__compare__value__|'
|
||||
|
||||
func _cannot_compare_text(v1, v2):
|
||||
return str('Cannot compare ', _strutils.types[typeof(v1)], ' with ',
|
||||
_strutils.types[typeof(v2)], '.')
|
||||
|
||||
func _make_missing_string(text):
|
||||
return '<missing ' + text + '>'
|
||||
|
||||
func _create_missing_result(v1, v2, text):
|
||||
var to_return = null
|
||||
var v1_str = format_value(v1)
|
||||
var v2_str = format_value(v2)
|
||||
|
||||
if(typeof(v1) == TYPE_STRING and v1 == MISSING):
|
||||
v1_str = _make_missing_string(text)
|
||||
to_return = _utils.CompareResult.new()
|
||||
elif(typeof(v2) == TYPE_STRING and v2 == MISSING):
|
||||
v2_str = _make_missing_string(text)
|
||||
to_return = _utils.CompareResult.new()
|
||||
|
||||
if(to_return != null):
|
||||
to_return.summary = str(v1_str, ' != ', v2_str)
|
||||
to_return.are_equal = false
|
||||
|
||||
return to_return
|
||||
|
||||
|
||||
func simple(v1, v2, missing_string=''):
|
||||
var missing_result = _create_missing_result(v1, v2, missing_string)
|
||||
if(missing_result != null):
|
||||
return missing_result
|
||||
|
||||
var result = _utils.CompareResult.new()
|
||||
var cmp_str = null
|
||||
var extra = ''
|
||||
|
||||
var tv1 = typeof(v1)
|
||||
var tv2 = typeof(v2)
|
||||
|
||||
# print(tv1, '::', tv2, ' ', _strutils.types[tv1], '::', _strutils.types[tv2])
|
||||
if(_should_compare_int_to_float and [TYPE_INT, TYPE_FLOAT].has(tv1) and [TYPE_INT, TYPE_FLOAT].has(tv2)):
|
||||
result.are_equal = v1 == v2
|
||||
elif([TYPE_STRING, TYPE_STRING_NAME].has(tv1) and [TYPE_STRING, TYPE_STRING_NAME].has(tv2)):
|
||||
result.are_equal = v1 == v2
|
||||
elif(_utils.are_datatypes_same(v1, v2)):
|
||||
result.are_equal = v1 == v2
|
||||
|
||||
if(typeof(v1) == TYPE_DICTIONARY or typeof(v1) == TYPE_ARRAY):
|
||||
var sub_result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP)
|
||||
result.summary = sub_result.get_short_summary()
|
||||
if(!sub_result.are_equal):
|
||||
extra = ".\n" + sub_result.get_short_summary()
|
||||
else:
|
||||
cmp_str = '!='
|
||||
result.are_equal = false
|
||||
extra = str('. ', _cannot_compare_text(v1, v2))
|
||||
|
||||
cmp_str = get_compare_symbol(result.are_equal)
|
||||
result.summary = str(format_value(v1), ' ', cmp_str, ' ', format_value(v2), extra)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func shallow(v1, v2):
|
||||
var result = null
|
||||
if(_utils.are_datatypes_same(v1, v2)):
|
||||
if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]):
|
||||
result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP)
|
||||
else:
|
||||
result = simple(v1, v2)
|
||||
else:
|
||||
result = simple(v1, v2)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func deep(v1, v2):
|
||||
var result = null
|
||||
|
||||
if(_utils.are_datatypes_same(v1, v2)):
|
||||
if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]):
|
||||
result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP)
|
||||
else:
|
||||
result = simple(v1, v2)
|
||||
else:
|
||||
result = simple(v1, v2)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func format_value(val, max_val_length=_max_length):
|
||||
return _strutils.truncate_string(_strutils.type2str(val), max_val_length)
|
||||
|
||||
|
||||
func compare(v1, v2, diff_type=_utils.DIFF.SIMPLE):
|
||||
var result = null
|
||||
if(diff_type == _utils.DIFF.SIMPLE):
|
||||
result = simple(v1, v2)
|
||||
elif(diff_type == _utils.DIFF.DEEP):
|
||||
result = deep(v1, v2)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func get_should_compare_int_to_float():
|
||||
return _should_compare_int_to_float
|
||||
|
||||
|
||||
func set_should_compare_int_to_float(should_compare_int_float):
|
||||
_should_compare_int_to_float = should_compare_int_float
|
||||
|
||||
|
||||
func get_compare_symbol(is_equal):
|
||||
if(is_equal):
|
||||
return '=='
|
||||
else:
|
||||
return '!='
|
||||
@@ -0,0 +1,70 @@
|
||||
var _are_equal = false
|
||||
var are_equal = false :
|
||||
get:
|
||||
return get_are_equal()
|
||||
set(val):
|
||||
set_are_equal(val)
|
||||
|
||||
var _summary = null
|
||||
var summary = null :
|
||||
get:
|
||||
return get_summary()
|
||||
set(val):
|
||||
set_summary(val)
|
||||
|
||||
var _max_differences = 30
|
||||
var max_differences = 30 :
|
||||
get:
|
||||
return get_max_differences()
|
||||
set(val):
|
||||
set_max_differences(val)
|
||||
|
||||
var _differences = {}
|
||||
var differences :
|
||||
get:
|
||||
return get_differences()
|
||||
set(val):
|
||||
set_differences(val)
|
||||
|
||||
func _block_set(which, val):
|
||||
push_error(str('cannot set ', which, ', value [', val, '] ignored.'))
|
||||
|
||||
func _to_string():
|
||||
return str(get_summary()) # could be null, gotta str it.
|
||||
|
||||
func get_are_equal():
|
||||
return _are_equal
|
||||
|
||||
func set_are_equal(r_eq):
|
||||
_are_equal = r_eq
|
||||
|
||||
func get_summary():
|
||||
return _summary
|
||||
|
||||
func set_summary(smry):
|
||||
_summary = smry
|
||||
|
||||
func get_total_count():
|
||||
pass
|
||||
|
||||
func get_different_count():
|
||||
pass
|
||||
|
||||
func get_short_summary():
|
||||
return summary
|
||||
|
||||
func get_max_differences():
|
||||
return _max_differences
|
||||
|
||||
func set_max_differences(max_diff):
|
||||
_max_differences = max_diff
|
||||
|
||||
func get_differences():
|
||||
return _differences
|
||||
|
||||
func set_differences(diffs):
|
||||
_block_set('differences', diffs)
|
||||
|
||||
func get_brackets():
|
||||
return null
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||
var _strutils = _utils.Strutils.new()
|
||||
const INDENT = ' '
|
||||
var _max_to_display = 30
|
||||
const ABSOLUTE_MAX_DISPLAYED = 10000
|
||||
const UNLIMITED = -1
|
||||
|
||||
|
||||
func _single_diff(diff, depth=0):
|
||||
var to_return = ""
|
||||
var brackets = diff.get_brackets()
|
||||
|
||||
if(brackets != null and !diff.are_equal):
|
||||
to_return = ''
|
||||
to_return += str(brackets.open, "\n",
|
||||
_strutils.indent_text(differences_to_s(diff.differences, depth), depth+1, INDENT), "\n",
|
||||
brackets.close)
|
||||
else:
|
||||
to_return = str(diff)
|
||||
|
||||
return to_return
|
||||
|
||||
|
||||
func make_it(diff):
|
||||
var to_return = ''
|
||||
if(diff.are_equal):
|
||||
to_return = diff.summary
|
||||
else:
|
||||
if(_max_to_display == ABSOLUTE_MAX_DISPLAYED):
|
||||
to_return = str(diff.get_value_1(), ' != ', diff.get_value_2())
|
||||
else:
|
||||
to_return = diff.get_short_summary()
|
||||
to_return += str("\n", _strutils.indent_text(_single_diff(diff, 0), 1, ' '))
|
||||
return to_return
|
||||
|
||||
|
||||
func differences_to_s(differences, depth=0):
|
||||
var to_return = ''
|
||||
var keys = differences.keys()
|
||||
keys.sort()
|
||||
var limit = min(_max_to_display, differences.size())
|
||||
|
||||
for i in range(limit):
|
||||
var key = keys[i]
|
||||
to_return += str(key, ": ", _single_diff(differences[key], depth))
|
||||
|
||||
if(i != limit -1):
|
||||
to_return += "\n"
|
||||
|
||||
if(differences.size() > _max_to_display):
|
||||
to_return += str("\n\n... ", differences.size() - _max_to_display, " more.")
|
||||
|
||||
return to_return
|
||||
|
||||
|
||||
func get_max_to_display():
|
||||
return _max_to_display
|
||||
|
||||
|
||||
func set_max_to_display(max_to_display):
|
||||
_max_to_display = max_to_display
|
||||
if(_max_to_display == UNLIMITED):
|
||||
_max_to_display = ABSOLUTE_MAX_DISPLAYED
|
||||
|
||||
158
samples/client/petstore/gdscript/addons/gut/diff_tool.gd
Normal file
158
samples/client/petstore/gdscript/addons/gut/diff_tool.gd
Normal file
@@ -0,0 +1,158 @@
|
||||
extends 'res://addons/gut/compare_result.gd'
|
||||
const INDENT = ' '
|
||||
enum {
|
||||
DEEP,
|
||||
SIMPLE
|
||||
}
|
||||
|
||||
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||
var _strutils = _utils.Strutils.new()
|
||||
var _compare = _utils.Comparator.new()
|
||||
var DiffTool = load('res://addons/gut/diff_tool.gd')
|
||||
|
||||
var _value_1 = null
|
||||
var _value_2 = null
|
||||
var _total_count = 0
|
||||
var _diff_type = null
|
||||
var _brackets = null
|
||||
var _valid = true
|
||||
var _desc_things = 'somethings'
|
||||
|
||||
# -------- comapre_result.gd "interface" ---------------------
|
||||
func set_are_equal(val):
|
||||
_block_set('are_equal', val)
|
||||
|
||||
func get_are_equal():
|
||||
if(!_valid):
|
||||
return null
|
||||
else:
|
||||
return differences.size() == 0
|
||||
|
||||
|
||||
func set_summary(val):
|
||||
_block_set('summary', val)
|
||||
|
||||
func get_summary():
|
||||
return summarize()
|
||||
|
||||
func get_different_count():
|
||||
return differences.size()
|
||||
|
||||
func get_total_count():
|
||||
return _total_count
|
||||
|
||||
func get_short_summary():
|
||||
var text = str(_strutils.truncate_string(str(_value_1), 50),
|
||||
' ', _compare.get_compare_symbol(are_equal), ' ',
|
||||
_strutils.truncate_string(str(_value_2), 50))
|
||||
if(!are_equal):
|
||||
text += str(' ', get_different_count(), ' of ', get_total_count(),
|
||||
' ', _desc_things, ' do not match.')
|
||||
return text
|
||||
|
||||
func get_brackets():
|
||||
return _brackets
|
||||
# -------- comapre_result.gd "interface" ---------------------
|
||||
|
||||
|
||||
func _invalidate():
|
||||
_valid = false
|
||||
differences = null
|
||||
|
||||
|
||||
func _init(v1,v2,diff_type=DEEP):
|
||||
_value_1 = v1
|
||||
_value_2 = v2
|
||||
_diff_type = diff_type
|
||||
_compare.set_should_compare_int_to_float(false)
|
||||
_find_differences(_value_1, _value_2)
|
||||
|
||||
|
||||
func _find_differences(v1, v2):
|
||||
if(_utils.are_datatypes_same(v1, v2)):
|
||||
if(typeof(v1) == TYPE_ARRAY):
|
||||
_brackets = {'open':'[', 'close':']'}
|
||||
_desc_things = 'indexes'
|
||||
_diff_array(v1, v2)
|
||||
elif(typeof(v2) == TYPE_DICTIONARY):
|
||||
_brackets = {'open':'{', 'close':'}'}
|
||||
_desc_things = 'keys'
|
||||
_diff_dictionary(v1, v2)
|
||||
else:
|
||||
_invalidate()
|
||||
_utils.get_logger().error('Only Arrays and Dictionaries are supported.')
|
||||
else:
|
||||
_invalidate()
|
||||
_utils.get_logger().error('Only Arrays and Dictionaries are supported.')
|
||||
|
||||
|
||||
func _diff_array(a1, a2):
|
||||
_total_count = max(a1.size(), a2.size())
|
||||
for i in range(a1.size()):
|
||||
var result = null
|
||||
if(i < a2.size()):
|
||||
if(_diff_type == DEEP):
|
||||
result = _compare.deep(a1[i], a2[i])
|
||||
else:
|
||||
result = _compare.simple(a1[i], a2[i])
|
||||
else:
|
||||
result = _compare.simple(a1[i], _compare.MISSING, 'index')
|
||||
|
||||
if(!result.are_equal):
|
||||
differences[i] = result
|
||||
|
||||
if(a1.size() < a2.size()):
|
||||
for i in range(a1.size(), a2.size()):
|
||||
differences[i] = _compare.simple(_compare.MISSING, a2[i], 'index')
|
||||
|
||||
|
||||
func _diff_dictionary(d1, d2):
|
||||
var d1_keys = d1.keys()
|
||||
var d2_keys = d2.keys()
|
||||
|
||||
# Process all the keys in d1
|
||||
_total_count += d1_keys.size()
|
||||
for key in d1_keys:
|
||||
if(!d2.has(key)):
|
||||
differences[key] = _compare.simple(d1[key], _compare.MISSING, 'key')
|
||||
else:
|
||||
d2_keys.remove_at(d2_keys.find(key))
|
||||
|
||||
var result = null
|
||||
if(_diff_type == DEEP):
|
||||
result = _compare.deep(d1[key], d2[key])
|
||||
else:
|
||||
result = _compare.simple(d1[key], d2[key])
|
||||
|
||||
if(!result.are_equal):
|
||||
differences[key] = result
|
||||
|
||||
# Process all the keys in d2 that didn't exist in d1
|
||||
_total_count += d2_keys.size()
|
||||
for i in range(d2_keys.size()):
|
||||
differences[d2_keys[i]] = _compare.simple(_compare.MISSING, d2[d2_keys[i]], 'key')
|
||||
|
||||
|
||||
func summarize():
|
||||
var summary = ''
|
||||
|
||||
if(are_equal):
|
||||
summary = get_short_summary()
|
||||
else:
|
||||
var formatter = load('res://addons/gut/diff_formatter.gd').new()
|
||||
formatter.set_max_to_display(max_differences)
|
||||
summary = formatter.make_it(self)
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
func get_diff_type():
|
||||
return _diff_type
|
||||
|
||||
|
||||
func get_value_1():
|
||||
return _value_1
|
||||
|
||||
|
||||
func get_value_2():
|
||||
return _value_2
|
||||
@@ -0,0 +1,6 @@
|
||||
{func_decleration}
|
||||
__gutdbl.spy_on('{method_name}', {param_array})
|
||||
if(__gutdbl.should_call_super('{method_name}', {param_array})):
|
||||
return {super_call}
|
||||
else:
|
||||
return __gutdbl.get_stubbed_return('{method_name}', {param_array})
|
||||
@@ -0,0 +1,4 @@
|
||||
{func_decleration}:
|
||||
super({super_params})
|
||||
__gutdbl.spy_on('{method_name}', {param_array})
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# ##############################################################################
|
||||
# Gut Doubled Script
|
||||
# ##############################################################################
|
||||
{extends}
|
||||
|
||||
{constants}
|
||||
|
||||
{properties}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# GUT stuff
|
||||
# ------------------------------------------------------------------------------
|
||||
var __gutdbl_values = {
|
||||
double = self,
|
||||
thepath = '{path}',
|
||||
subpath = '{subpath}',
|
||||
stubber = {stubber_id},
|
||||
spy = {spy_id},
|
||||
gut = {gut_id},
|
||||
from_singleton = '{singleton_name}',
|
||||
is_partial = {is_partial},
|
||||
}
|
||||
var __gutdbl = load('res://addons/gut/double_tools.gd').new(__gutdbl_values)
|
||||
|
||||
# Here so other things can check for a method to know if this is a double.
|
||||
func __gutdbl_check_method__():
|
||||
pass
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Doubled Methods
|
||||
# ------------------------------------------------------------------------------
|
||||
52
samples/client/petstore/gdscript/addons/gut/double_tools.gd
Normal file
52
samples/client/petstore/gdscript/addons/gut/double_tools.gd
Normal file
@@ -0,0 +1,52 @@
|
||||
var thepath = ''
|
||||
var subpath = ''
|
||||
var stubber = null
|
||||
var spy = null
|
||||
var gut = null
|
||||
var from_singleton = null
|
||||
var is_partial = null
|
||||
var double = null
|
||||
|
||||
const NO_DEFAULT_VALUE = '!__gut__no__default__value__!'
|
||||
func from_id(inst_id):
|
||||
if(inst_id == -1):
|
||||
return null
|
||||
else:
|
||||
return instance_from_id(inst_id)
|
||||
|
||||
func should_call_super(method_name, called_with):
|
||||
if(stubber != null):
|
||||
return stubber.should_call_super(double, method_name, called_with)
|
||||
else:
|
||||
return false
|
||||
|
||||
func spy_on(method_name, called_with):
|
||||
if(spy != null):
|
||||
spy.add_call(double, method_name, called_with)
|
||||
|
||||
func get_stubbed_return(method_name, called_with):
|
||||
if(stubber != null):
|
||||
return stubber.get_return(double, method_name, called_with)
|
||||
else:
|
||||
return null
|
||||
|
||||
func default_val(method_name, p_index, default_val=NO_DEFAULT_VALUE):
|
||||
if(stubber != null):
|
||||
return stubber.get_default_value(double, method_name, p_index)
|
||||
else:
|
||||
return null
|
||||
|
||||
func _init(values=null):
|
||||
if(values != null):
|
||||
double = values.double
|
||||
thepath = values.thepath
|
||||
subpath = values.subpath
|
||||
stubber = from_id(values.stubber)
|
||||
spy = from_id(values.spy)
|
||||
gut = from_id(values.gut)
|
||||
from_singleton = values.from_singleton
|
||||
is_partial = values.is_partial
|
||||
|
||||
if(gut != null):
|
||||
gut.get_autofree().add_free(double)
|
||||
|
||||
321
samples/client/petstore/gdscript/addons/gut/doubler.gd
Normal file
321
samples/client/petstore/gdscript/addons/gut/doubler.gd
Normal file
@@ -0,0 +1,321 @@
|
||||
# ##############################################################################
|
||||
#(G)odot (U)nit (T)est class
|
||||
#
|
||||
# ##############################################################################
|
||||
# The MIT License (MIT)
|
||||
# =====================
|
||||
#
|
||||
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# ##############################################################################
|
||||
# Description
|
||||
# -----------
|
||||
# ##############################################################################
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# A stroke of genius if I do say so. This allows for doubling a scene without
|
||||
# having to write any files. By overloading the "instantiate" method we can
|
||||
# make whatever we want.
|
||||
# ------------------------------------------------------------------------------
|
||||
class PackedSceneDouble:
|
||||
extends PackedScene
|
||||
var _script = null
|
||||
var _scene = null
|
||||
|
||||
func set_script_obj(obj):
|
||||
_script = obj
|
||||
|
||||
func instantiate(edit_state=0):
|
||||
var inst = _scene.instantiate(edit_state)
|
||||
if(_script != null):
|
||||
inst.set_script(_script)
|
||||
return inst
|
||||
|
||||
func load_scene(path):
|
||||
_scene = load(path)
|
||||
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# START Doubler
|
||||
# ------------------------------------------------------------------------------
|
||||
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||
var _base_script_text = _utils.get_file_as_text('res://addons/gut/double_templates/script_template.txt')
|
||||
var _script_collector = _utils.ScriptCollector.new()
|
||||
# used by tests for debugging purposes.
|
||||
var print_source = false
|
||||
var inner_class_registry = _utils.InnerClassRegistry.new()
|
||||
|
||||
# ###############
|
||||
# Properties
|
||||
# ###############
|
||||
var _stubber = _utils.Stubber.new()
|
||||
func get_stubber():
|
||||
return _stubber
|
||||
func set_stubber(stubber):
|
||||
_stubber = stubber
|
||||
|
||||
var _lgr = _utils.get_logger()
|
||||
func get_logger():
|
||||
return _lgr
|
||||
func set_logger(logger):
|
||||
_lgr = logger
|
||||
_method_maker.set_logger(logger)
|
||||
|
||||
var _spy = null
|
||||
func get_spy():
|
||||
return _spy
|
||||
func set_spy(spy):
|
||||
_spy = spy
|
||||
|
||||
var _gut = null
|
||||
func get_gut():
|
||||
return _gut
|
||||
func set_gut(gut):
|
||||
_gut = gut
|
||||
|
||||
var _strategy = null
|
||||
func get_strategy():
|
||||
return _strategy
|
||||
func set_strategy(strategy):
|
||||
_strategy = strategy
|
||||
|
||||
|
||||
var _method_maker = _utils.MethodMaker.new()
|
||||
func get_method_maker():
|
||||
return _method_maker
|
||||
|
||||
var _ignored_methods = _utils.OneToMany.new()
|
||||
func get_ignored_methods():
|
||||
return _ignored_methods
|
||||
|
||||
# ###############
|
||||
# Private
|
||||
# ###############
|
||||
func _init(strategy=_utils.DOUBLE_STRATEGY.SCRIPT_ONLY):
|
||||
set_logger(_utils.get_logger())
|
||||
_strategy = strategy
|
||||
|
||||
|
||||
func _get_indented_line(indents, text):
|
||||
var to_return = ''
|
||||
for _i in range(indents):
|
||||
to_return += "\t"
|
||||
return str(to_return, text, "\n")
|
||||
|
||||
|
||||
func _stub_to_call_super(parsed, method_name):
|
||||
if(_utils.non_super_methods.has(method_name)):
|
||||
return
|
||||
|
||||
var params = _utils.StubParams.new(parsed.script_path, method_name, parsed.subpath)
|
||||
params.to_call_super()
|
||||
_stubber.add_stub(params)
|
||||
|
||||
|
||||
func _get_base_script_text(parsed, override_path, partial):
|
||||
var path = parsed.script_path
|
||||
if(override_path != null):
|
||||
path = override_path
|
||||
|
||||
var stubber_id = -1
|
||||
if(_stubber != null):
|
||||
stubber_id = _stubber.get_instance_id()
|
||||
|
||||
var spy_id = -1
|
||||
if(_spy != null):
|
||||
spy_id = _spy.get_instance_id()
|
||||
|
||||
var gut_id = -1
|
||||
if(_gut != null):
|
||||
gut_id = _gut.get_instance_id()
|
||||
|
||||
var extends_text = parsed.get_extends_text()
|
||||
|
||||
var values = {
|
||||
# Top sections
|
||||
"extends":extends_text,
|
||||
"constants":'',#obj_info.get_constants_text(),
|
||||
"properties":'',#obj_info.get_properties_text(),
|
||||
|
||||
# metadata values
|
||||
"path":path,
|
||||
"subpath":_utils.nvl(parsed.subpath, ''),
|
||||
"stubber_id":stubber_id,
|
||||
"spy_id":spy_id,
|
||||
"gut_id":gut_id,
|
||||
"singleton_name":'',#_utils.nvl(obj_info.get_singleton_name(), ''),
|
||||
"is_partial":partial,
|
||||
}
|
||||
|
||||
return _base_script_text.format(values)
|
||||
|
||||
|
||||
func _is_valid_double_method(parsed_script, parsed_method):
|
||||
return !parsed_method.is_accessor() and \
|
||||
!parsed_method.is_black_listed() and \
|
||||
!_ignored_methods.has(parsed_script.resource, parsed_method.meta.name)
|
||||
|
||||
func _create_double(parsed, strategy, override_path, partial):
|
||||
var base_script = _get_base_script_text(parsed, override_path, partial)
|
||||
var super_name = ""
|
||||
var path = ""
|
||||
|
||||
path = parsed.script_path
|
||||
var dbl_src = ""
|
||||
dbl_src += base_script
|
||||
|
||||
for method in parsed.get_local_methods():
|
||||
if(_is_valid_double_method(parsed, method)):
|
||||
var mthd = parsed.get_local_method(method.meta.name)
|
||||
if(parsed.is_native):
|
||||
dbl_src += _get_func_text(method.meta, parsed.resource, super_name)
|
||||
else:
|
||||
dbl_src += _get_func_text(method.meta, path, super_name)
|
||||
|
||||
if(strategy == _utils.DOUBLE_STRATEGY.INCLUDE_SUPER):
|
||||
for method in parsed.get_super_methods():
|
||||
if(_is_valid_double_method(parsed, method)):
|
||||
_stub_to_call_super(parsed, method.meta.name)
|
||||
if(parsed.is_native):
|
||||
dbl_src += _get_func_text(method.meta, parsed.resource, super_name)
|
||||
else:
|
||||
dbl_src += _get_func_text(method.meta, path, super_name)
|
||||
|
||||
if(print_source):
|
||||
print(_utils.add_line_numbers(dbl_src))
|
||||
|
||||
var DblClass = _utils.create_script_from_source(dbl_src)
|
||||
if(_stubber != null):
|
||||
_stub_method_default_values(DblClass, parsed, strategy)
|
||||
|
||||
return DblClass
|
||||
|
||||
|
||||
func _stub_method_default_values(which, parsed, strategy):
|
||||
for method in parsed.get_local_methods():
|
||||
if(!method.is_black_listed() && !_ignored_methods.has(parsed.resource, method.meta.name)):
|
||||
_stubber.stub_defaults_from_meta(parsed.script_path, method.meta)
|
||||
|
||||
|
||||
|
||||
func _double_scene_and_script(scene, strategy, partial):
|
||||
var to_return = PackedSceneDouble.new()
|
||||
to_return.load_scene(scene.get_path())
|
||||
|
||||
var script_obj = _utils.get_scene_script_object(scene)
|
||||
if(script_obj != null):
|
||||
var script_dbl = null
|
||||
if(partial):
|
||||
script_dbl = _partial_double(script_obj, strategy, scene.get_path())
|
||||
else:
|
||||
script_dbl = _double(script_obj, strategy, scene.get_path())
|
||||
to_return.set_script_obj(script_dbl)
|
||||
|
||||
return to_return
|
||||
|
||||
|
||||
func _get_inst_id_ref_str(inst):
|
||||
var ref_str = 'null'
|
||||
if(inst):
|
||||
ref_str = str('instance_from_id(', inst.get_instance_id(),')')
|
||||
return ref_str
|
||||
|
||||
|
||||
func _get_func_text(method_hash, path, super_=""):
|
||||
var override_count = null;
|
||||
if(_stubber != null):
|
||||
override_count = _stubber.get_parameter_count(path, method_hash.name)
|
||||
|
||||
var text = _method_maker.get_function_text(method_hash, path, override_count, super_) + "\n"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
func _parse_script(obj):
|
||||
var parsed = null
|
||||
|
||||
if(_utils.is_inner_class(obj)):
|
||||
if(inner_class_registry.has(obj)):
|
||||
parsed = _script_collector.parse(inner_class_registry.get_base_resource(obj), obj)
|
||||
else:
|
||||
_lgr.error('Doubling Inner Classes requires you register them first. Call register_inner_classes passing the script that contains the inner class.')
|
||||
else:
|
||||
parsed = _script_collector.parse(obj)
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
# Override path is used with scenes.
|
||||
func _double(obj, strategy, override_path=null):
|
||||
var parsed = _parse_script(obj)
|
||||
if(parsed != null):
|
||||
return _create_double(parsed, strategy, override_path, false)
|
||||
|
||||
|
||||
func _partial_double(obj, strategy, override_path=null):
|
||||
var parsed = _parse_script(obj)
|
||||
if(parsed != null):
|
||||
return _create_double(parsed, strategy, override_path, true)
|
||||
|
||||
|
||||
# -------------------------
|
||||
# Public
|
||||
# -------------------------
|
||||
|
||||
# double a script/object
|
||||
func double(obj, strategy=_strategy):
|
||||
return _double(obj, strategy)
|
||||
|
||||
func partial_double(obj, strategy=_strategy):
|
||||
return _partial_double(obj, strategy)
|
||||
|
||||
|
||||
# double a scene
|
||||
func double_scene(scene, strategy=_strategy):
|
||||
return _double_scene_and_script(scene, strategy, false)
|
||||
|
||||
func partial_double_scene(scene, strategy=_strategy):
|
||||
return _double_scene_and_script(scene, strategy, true)
|
||||
|
||||
|
||||
func double_gdnative(which):
|
||||
return _double(which, _utils.DOUBLE_STRATEGY.INCLUDE_SUPER)
|
||||
|
||||
func partial_double_gdnative(which):
|
||||
return _partial_double(which, _utils.DOUBLE_STRATEGY.INCLUDE_SUPER)
|
||||
|
||||
|
||||
func double_inner(parent, inner, strategy=_strategy):
|
||||
var parsed = _script_collector.parse(parent, inner)
|
||||
return _create_double(parsed, strategy, null, false)
|
||||
|
||||
|
||||
func partial_double_inner(parent, inner, strategy=_strategy):
|
||||
var parsed = _script_collector.parse(parent, inner)
|
||||
return _create_double(parsed, strategy, null, true)
|
||||
|
||||
|
||||
func add_ignored_method(obj, method_name):
|
||||
_ignored_methods.add(obj, method_name)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
94
samples/client/petstore/gdscript/addons/gut/fonts/OFL.txt
Normal file
94
samples/client/petstore/gdscript/addons/gut/fonts/OFL.txt
Normal file
@@ -0,0 +1,94 @@
|
||||
Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com),
|
||||
with Reserved Font Name Anonymous Pro.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,82 @@
|
||||
@tool
|
||||
extends Window
|
||||
|
||||
@onready var _ctrls = {
|
||||
run_all = $Layout/CRunAll/ShortcutButton,
|
||||
run_current_script = $Layout/CRunCurrentScript/ShortcutButton,
|
||||
run_current_inner = $Layout/CRunCurrentInner/ShortcutButton,
|
||||
run_current_test = $Layout/CRunCurrentTest/ShortcutButton,
|
||||
panel_button = $Layout/CPanelButton/ShortcutButton,
|
||||
}
|
||||
|
||||
func _ready():
|
||||
for key in _ctrls:
|
||||
var sc_button = _ctrls[key]
|
||||
sc_button.connect('start_edit', _on_edit_start.bind(sc_button))
|
||||
sc_button.connect('end_edit', _on_edit_end)
|
||||
|
||||
|
||||
# show dialog when running scene from editor.
|
||||
if(get_parent() == get_tree().root):
|
||||
popup_centered()
|
||||
|
||||
# ------------
|
||||
# Events
|
||||
# ------------
|
||||
func _on_Hide_pressed():
|
||||
hide()
|
||||
|
||||
func _on_edit_start(which):
|
||||
for key in _ctrls:
|
||||
var sc_button = _ctrls[key]
|
||||
if(sc_button != which):
|
||||
sc_button.disable_set(true)
|
||||
sc_button.disable_clear(true)
|
||||
|
||||
func _on_edit_end():
|
||||
for key in _ctrls:
|
||||
var sc_button = _ctrls[key]
|
||||
sc_button.disable_set(false)
|
||||
sc_button.disable_clear(false)
|
||||
|
||||
# ------------
|
||||
# Public
|
||||
# ------------
|
||||
func get_run_all():
|
||||
return _ctrls.run_all.get_shortcut()
|
||||
|
||||
func get_run_current_script():
|
||||
return _ctrls.run_current_script.get_shortcut()
|
||||
|
||||
func get_run_current_inner():
|
||||
return _ctrls.run_current_inner.get_shortcut()
|
||||
|
||||
func get_run_current_test():
|
||||
return _ctrls.run_current_test.get_shortcut()
|
||||
|
||||
func get_panel_button():
|
||||
return _ctrls.panel_button.get_shortcut()
|
||||
|
||||
|
||||
func save_shortcuts(path):
|
||||
var f = ConfigFile.new()
|
||||
|
||||
f.set_value('main', 'run_all', _ctrls.run_all.get_shortcut())
|
||||
f.set_value('main', 'run_current_script', _ctrls.run_current_script.get_shortcut())
|
||||
f.set_value('main', 'run_current_inner', _ctrls.run_current_inner.get_shortcut())
|
||||
f.set_value('main', 'run_current_test', _ctrls.run_current_test.get_shortcut())
|
||||
f.set_value('main', 'panel_button', _ctrls.panel_button.get_shortcut())
|
||||
|
||||
f.save(path)
|
||||
|
||||
|
||||
func load_shortcuts(path):
|
||||
var emptyShortcut = Shortcut.new()
|
||||
var f = ConfigFile.new()
|
||||
f.load(path)
|
||||
|
||||
_ctrls.run_all.set_shortcut(f.get_value('main', 'run_all', emptyShortcut))
|
||||
_ctrls.run_current_script.set_shortcut(f.get_value('main', 'run_current_script', emptyShortcut))
|
||||
_ctrls.run_current_inner.set_shortcut(f.get_value('main', 'run_current_inner', emptyShortcut))
|
||||
_ctrls.run_current_test.set_shortcut(f.get_value('main', 'run_current_test', emptyShortcut))
|
||||
_ctrls.panel_button.set_shortcut(f.get_value('main', 'panel_button', emptyShortcut))
|
||||
@@ -0,0 +1,150 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bsk32dh41b4gs"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://sfb1fw8j6ufu" path="res://addons/gut/gui/ShortcutButton.tscn" id="1"]
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/BottomPanelShortcuts.gd" id="2"]
|
||||
|
||||
[node name="BottomPanelShortcuts" type="Popup"]
|
||||
title = "Shortcuts"
|
||||
size = Vector2i(500, 350)
|
||||
visible = true
|
||||
exclusive = true
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Layout" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 5.0
|
||||
offset_right = -5.0
|
||||
offset_bottom = 2.0
|
||||
|
||||
[node name="TopPad" type="CenterContainer" parent="Layout"]
|
||||
custom_minimum_size = Vector2(0, 5)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label2" type="Label" parent="Layout"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
text = "Always Active"
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="Layout/Label2"]
|
||||
show_behind_parent = true
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.196078)
|
||||
|
||||
[node name="CPanelButton" type="HBoxContainer" parent="Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Layout/CPanelButton"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 7
|
||||
text = "Show/Hide GUT Panel"
|
||||
|
||||
[node name="ShortcutButton" parent="Layout/CPanelButton" instance=ExtResource("1")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="GutPanelPad" type="CenterContainer" parent="Layout"]
|
||||
custom_minimum_size = Vector2(0, 5)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Layout"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
text = "Only Active When GUT Panel Shown"
|
||||
|
||||
[node name="ColorRect2" type="ColorRect" parent="Layout/Label"]
|
||||
show_behind_parent = true
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0, 0, 0, 0.196078)
|
||||
|
||||
[node name="TopPad2" type="CenterContainer" parent="Layout"]
|
||||
custom_minimum_size = Vector2(0, 5)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CRunAll" type="HBoxContainer" parent="Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Layout/CRunAll"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 7
|
||||
text = "Run All"
|
||||
|
||||
[node name="ShortcutButton" parent="Layout/CRunAll" instance=ExtResource("1")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CRunCurrentScript" type="HBoxContainer" parent="Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Layout/CRunCurrentScript"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 7
|
||||
text = "Run Current Script"
|
||||
|
||||
[node name="ShortcutButton" parent="Layout/CRunCurrentScript" instance=ExtResource("1")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CRunCurrentInner" type="HBoxContainer" parent="Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Layout/CRunCurrentInner"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 7
|
||||
text = "Run Current Inner Class"
|
||||
|
||||
[node name="ShortcutButton" parent="Layout/CRunCurrentInner" instance=ExtResource("1")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CRunCurrentTest" type="HBoxContainer" parent="Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Layout/CRunCurrentTest"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 7
|
||||
text = "Run Current Test"
|
||||
|
||||
[node name="ShortcutButton" parent="Layout/CRunCurrentTest" instance=ExtResource("1")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CenterContainer2" type="CenterContainer" parent="Layout"]
|
||||
custom_minimum_size = Vector2(0, 5)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="ShiftDisclaimer" type="Label" parent="Layout"]
|
||||
layout_mode = 2
|
||||
text = "\"Shift\" cannot be the only modifier for a shortcut."
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Layout/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Hide" type="Button" parent="Layout/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(60, 30)
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[node name="BottomPad" type="CenterContainer" parent="Layout"]
|
||||
custom_minimum_size = Vector2(0, 10)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[connection signal="pressed" from="Layout/HBoxContainer/Hide" to="." method="_on_Hide_pressed"]
|
||||
@@ -0,0 +1,367 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const RUNNER_JSON_PATH = 'res://.gut_editor_config.json'
|
||||
const RESULT_FILE = 'user://.gut_editor.bbcode'
|
||||
const RESULT_JSON = 'user://.gut_editor.json'
|
||||
const SHORTCUTS_PATH = 'res://.gut_editor_shortcuts.cfg'
|
||||
|
||||
var TestScript = load('res://addons/gut/test.gd')
|
||||
var GutConfigGui = load('res://addons/gut/gui/gut_config_gui.gd')
|
||||
var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd')
|
||||
|
||||
var _interface = null;
|
||||
var _is_running = false;
|
||||
var _gut_config = load('res://addons/gut/gut_config.gd').new()
|
||||
var _gut_config_gui = null
|
||||
var _gut_plugin = null
|
||||
var _light_color = Color(0, 0, 0, .5)
|
||||
var _panel_button = null
|
||||
var _last_selected_path = null
|
||||
|
||||
|
||||
@onready var _ctrls = {
|
||||
output = $layout/RSplit/CResults/TabBar/OutputText.get_rich_text_edit(),
|
||||
output_ctrl = $layout/RSplit/CResults/TabBar/OutputText,
|
||||
run_button = $layout/ControlBar/RunAll,
|
||||
shortcuts_button = $layout/ControlBar/Shortcuts,
|
||||
|
||||
settings_button = $layout/ControlBar/Settings,
|
||||
run_results_button = $layout/ControlBar/RunResultsBtn,
|
||||
output_button = $layout/ControlBar/OutputBtn,
|
||||
|
||||
settings = $layout/RSplit/sc/Settings,
|
||||
shortcut_dialog = $BottomPanelShortcuts,
|
||||
light = $layout/RSplit/CResults/ControlBar/Light3D,
|
||||
results = {
|
||||
bar = $layout/RSplit/CResults/ControlBar,
|
||||
passing = $layout/RSplit/CResults/ControlBar/Passing/value,
|
||||
failing = $layout/RSplit/CResults/ControlBar/Failing/value,
|
||||
pending = $layout/RSplit/CResults/ControlBar/Pending/value,
|
||||
errors = $layout/RSplit/CResults/ControlBar/Errors/value,
|
||||
warnings = $layout/RSplit/CResults/ControlBar/Warnings/value,
|
||||
orphans = $layout/RSplit/CResults/ControlBar/Orphans/value
|
||||
},
|
||||
run_at_cursor = $layout/ControlBar/RunAtCursor,
|
||||
run_results = $layout/RSplit/CResults/TabBar/RunResults
|
||||
}
|
||||
|
||||
|
||||
func _init():
|
||||
_gut_config.load_panel_options(RUNNER_JSON_PATH)
|
||||
|
||||
|
||||
func _ready():
|
||||
_ctrls.results.bar.connect('draw', _on_results_bar_draw.bind(_ctrls.results.bar))
|
||||
hide_settings(!_ctrls.settings_button.button_pressed)
|
||||
_gut_config_gui = GutConfigGui.new(_ctrls.settings)
|
||||
_gut_config_gui.set_options(_gut_config.options)
|
||||
|
||||
_apply_options_to_controls()
|
||||
|
||||
_ctrls.shortcuts_button.icon = get_theme_icon('Shortcut', 'EditorIcons')
|
||||
_ctrls.settings_button.icon = get_theme_icon('Tools', 'EditorIcons')
|
||||
_ctrls.run_results_button.icon = get_theme_icon('AnimationTrackGroup', 'EditorIcons') # Tree
|
||||
_ctrls.output_button.icon = get_theme_icon('Font', 'EditorIcons')
|
||||
|
||||
_ctrls.run_results.set_output_control(_ctrls.output_ctrl)
|
||||
_ctrls.run_results.set_font(
|
||||
_gut_config.options.panel_options.font_name,
|
||||
_gut_config.options.panel_options.font_size)
|
||||
|
||||
var check_import = load('res://addons/gut/images/red.png')
|
||||
if(check_import == null):
|
||||
_ctrls.run_results.add_centered_text("GUT got some new images that are not imported yet. Please restart Godot.")
|
||||
print('GUT got some new images that are not imported yet. Please restart Godot.')
|
||||
else:
|
||||
_ctrls.run_results.add_centered_text("Let's run some tests!")
|
||||
|
||||
|
||||
func _apply_options_to_controls():
|
||||
hide_settings(_gut_config.options.panel_options.hide_settings)
|
||||
hide_result_tree(_gut_config.options.panel_options.hide_result_tree)
|
||||
hide_output_text(_gut_config.options.panel_options.hide_output_text)
|
||||
|
||||
_ctrls.output_ctrl.set_use_colors(_gut_config.options.panel_options.use_colors)
|
||||
_ctrls.output_ctrl.set_all_fonts(_gut_config.options.panel_options.font_name)
|
||||
_ctrls.output_ctrl.set_font_size(_gut_config.options.panel_options.font_size)
|
||||
|
||||
_ctrls.run_results.set_font(
|
||||
_gut_config.options.panel_options.font_name,
|
||||
_gut_config.options.panel_options.font_size)
|
||||
_ctrls.run_results.set_show_orphans(!_gut_config.options.hide_orphans)
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if(_is_running):
|
||||
if(!_interface.is_playing_scene()):
|
||||
_is_running = false
|
||||
_ctrls.output_ctrl.add_text("\ndone")
|
||||
load_result_output()
|
||||
_gut_plugin.make_bottom_panel_item_visible(self)
|
||||
|
||||
# ---------------
|
||||
# Private
|
||||
# ---------------
|
||||
|
||||
func load_shortcuts():
|
||||
_ctrls.shortcut_dialog.load_shortcuts(SHORTCUTS_PATH)
|
||||
_apply_shortcuts()
|
||||
|
||||
|
||||
func _is_test_script(script):
|
||||
var from = script.get_base_script()
|
||||
while(from and from.resource_path != 'res://addons/gut/test.gd'):
|
||||
from = from.get_base_script()
|
||||
|
||||
return from != null
|
||||
|
||||
|
||||
func _show_errors(errs):
|
||||
_ctrls.output_ctrl.clear()
|
||||
var text = "Cannot run tests, you have a configuration error:\n"
|
||||
for e in errs:
|
||||
text += str('* ', e, "\n")
|
||||
text += "Check your settings ----->"
|
||||
_ctrls.output_ctrl.add_text(text)
|
||||
hide_output_text(false)
|
||||
hide_settings(false)
|
||||
|
||||
|
||||
func _save_config():
|
||||
_gut_config.options = _gut_config_gui.get_options(_gut_config.options)
|
||||
_gut_config.options.panel_options.hide_settings = !_ctrls.settings_button.button_pressed
|
||||
_gut_config.options.panel_options.hide_result_tree = !_ctrls.run_results_button.button_pressed
|
||||
_gut_config.options.panel_options.hide_output_text = !_ctrls.output_button.button_pressed
|
||||
_gut_config.options.panel_options.use_colors = _ctrls.output_ctrl.get_use_colors()
|
||||
|
||||
var w_result = _gut_config.write_options(RUNNER_JSON_PATH)
|
||||
if(w_result != OK):
|
||||
push_error(str('Could not write options to ', RUNNER_JSON_PATH, ': ', w_result))
|
||||
return;
|
||||
|
||||
|
||||
func _run_tests():
|
||||
var issues = _gut_config_gui.get_config_issues()
|
||||
if(issues.size() > 0):
|
||||
_show_errors(issues)
|
||||
return
|
||||
|
||||
write_file(RESULT_FILE, 'Run in progress')
|
||||
_save_config()
|
||||
_apply_options_to_controls()
|
||||
|
||||
_ctrls.output_ctrl.clear()
|
||||
_ctrls.run_results.clear()
|
||||
_ctrls.run_results.add_centered_text('Running...')
|
||||
|
||||
_interface.play_custom_scene('res://addons/gut/gui/GutRunner.tscn')
|
||||
_is_running = true
|
||||
_ctrls.output_ctrl.add_text('Running...')
|
||||
|
||||
|
||||
func _apply_shortcuts():
|
||||
_ctrls.run_button.shortcut = _ctrls.shortcut_dialog.get_run_all()
|
||||
|
||||
_ctrls.run_at_cursor.get_script_button().shortcut = \
|
||||
_ctrls.shortcut_dialog.get_run_current_script()
|
||||
_ctrls.run_at_cursor.get_inner_button().shortcut = \
|
||||
_ctrls.shortcut_dialog.get_run_current_inner()
|
||||
_ctrls.run_at_cursor.get_test_button().shortcut = \
|
||||
_ctrls.shortcut_dialog.get_run_current_test()
|
||||
|
||||
_panel_button.shortcut = _ctrls.shortcut_dialog.get_panel_button()
|
||||
|
||||
|
||||
func _run_all():
|
||||
_gut_config.options.selected = null
|
||||
_gut_config.options.inner_class = null
|
||||
_gut_config.options.unit_test_name = null
|
||||
|
||||
_run_tests()
|
||||
|
||||
|
||||
# ---------------
|
||||
# Events
|
||||
# ---------------
|
||||
func _on_results_bar_draw(bar):
|
||||
bar.draw_rect(Rect2(Vector2(0, 0), bar.size), Color(0, 0, 0, .2))
|
||||
|
||||
|
||||
func _on_Light_draw():
|
||||
var l = _ctrls.light
|
||||
l.draw_circle(Vector2(l.size.x / 2, l.size.y / 2), l.size.x / 2, _light_color)
|
||||
|
||||
|
||||
func _on_editor_script_changed(script):
|
||||
if(script):
|
||||
set_current_script(script)
|
||||
|
||||
|
||||
func _on_RunAll_pressed():
|
||||
_run_all()
|
||||
|
||||
|
||||
func _on_Shortcuts_pressed():
|
||||
_ctrls.shortcut_dialog.popup_centered()
|
||||
|
||||
func _on_bottom_panel_shortcuts_visibility_changed():
|
||||
_apply_shortcuts()
|
||||
_ctrls.shortcut_dialog.save_shortcuts(SHORTCUTS_PATH)
|
||||
|
||||
func _on_RunAtCursor_run_tests(what):
|
||||
_gut_config.options.selected = what.script
|
||||
_gut_config.options.inner_class = what.inner_class
|
||||
_gut_config.options.unit_test_name = what.test_method
|
||||
|
||||
_run_tests()
|
||||
|
||||
|
||||
func _on_Settings_pressed():
|
||||
hide_settings(!_ctrls.settings_button.button_pressed)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _on_OutputBtn_pressed():
|
||||
hide_output_text(!_ctrls.output_button.button_pressed)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _on_RunResultsBtn_pressed():
|
||||
hide_result_tree(! _ctrls.run_results_button.button_pressed)
|
||||
_save_config()
|
||||
|
||||
|
||||
# Currently not used, but will be when I figure out how to put
|
||||
# colors into the text results
|
||||
func _on_UseColors_pressed():
|
||||
pass
|
||||
|
||||
# ---------------
|
||||
# Public
|
||||
# ---------------
|
||||
func hide_result_tree(should):
|
||||
_ctrls.run_results.visible = !should
|
||||
_ctrls.run_results_button.button_pressed = !should
|
||||
|
||||
|
||||
func hide_settings(should):
|
||||
var s_scroll = _ctrls.settings.get_parent()
|
||||
s_scroll.visible = !should
|
||||
|
||||
# collapse only collapses the first control, so we move
|
||||
# settings around to be the collapsed one
|
||||
if(should):
|
||||
s_scroll.get_parent().move_child(s_scroll, 0)
|
||||
else:
|
||||
s_scroll.get_parent().move_child(s_scroll, 1)
|
||||
|
||||
$layout/RSplit.collapsed = should
|
||||
_ctrls.settings_button.button_pressed = !should
|
||||
|
||||
|
||||
func hide_output_text(should):
|
||||
$layout/RSplit/CResults/TabBar/OutputText.visible = !should
|
||||
_ctrls.output_button.button_pressed = !should
|
||||
|
||||
|
||||
func load_result_output():
|
||||
_ctrls.output_ctrl.load_file(RESULT_FILE)
|
||||
|
||||
var summary = get_file_as_text(RESULT_JSON)
|
||||
var test_json_conv = JSON.new()
|
||||
if (test_json_conv.parse(summary) != OK):
|
||||
return
|
||||
var results = test_json_conv.get_data()
|
||||
|
||||
_ctrls.run_results.load_json_results(results)
|
||||
|
||||
var summary_json = results['test_scripts']['props']
|
||||
_ctrls.results.passing.text = str(summary_json.passing)
|
||||
_ctrls.results.passing.get_parent().visible = true
|
||||
|
||||
_ctrls.results.failing.text = str(summary_json.failures)
|
||||
_ctrls.results.failing.get_parent().visible = true
|
||||
|
||||
_ctrls.results.pending.text = str(summary_json.pending)
|
||||
_ctrls.results.pending.get_parent().visible = _ctrls.results.pending.text != '0'
|
||||
|
||||
_ctrls.results.errors.text = str(summary_json.errors)
|
||||
_ctrls.results.errors.get_parent().visible = _ctrls.results.errors.text != '0'
|
||||
|
||||
_ctrls.results.warnings.text = str(summary_json.warnings)
|
||||
_ctrls.results.warnings.get_parent().visible = _ctrls.results.warnings.text != '0'
|
||||
|
||||
_ctrls.results.orphans.text = str(summary_json.orphans)
|
||||
_ctrls.results.orphans.get_parent().visible = _ctrls.results.orphans.text != '0' and !_gut_config.options.hide_orphans
|
||||
|
||||
if(summary_json.tests == 0):
|
||||
_light_color = Color(1, 0, 0, .75)
|
||||
elif(summary_json.failures != 0):
|
||||
_light_color = Color(1, 0, 0, .75)
|
||||
elif(summary_json.pending != 0):
|
||||
_light_color = Color(1, 1, 0, .75)
|
||||
else:
|
||||
_light_color = Color(0, 1, 0, .75)
|
||||
_ctrls.light.visible = true
|
||||
_ctrls.light.queue_redraw()
|
||||
|
||||
|
||||
func set_current_script(script):
|
||||
if(script):
|
||||
if(_is_test_script(script)):
|
||||
var file = script.resource_path.get_file()
|
||||
_last_selected_path = script.resource_path.get_file()
|
||||
_ctrls.run_at_cursor.activate_for_script(script.resource_path)
|
||||
|
||||
|
||||
func set_interface(value):
|
||||
_interface = value
|
||||
_interface.get_script_editor().connect("editor_script_changed",Callable(self,'_on_editor_script_changed'))
|
||||
|
||||
var ste = ScriptTextEditors.new(_interface.get_script_editor())
|
||||
_ctrls.run_results.set_interface(_interface)
|
||||
_ctrls.run_results.set_script_text_editors(ste)
|
||||
_ctrls.run_at_cursor.set_script_text_editors(ste)
|
||||
set_current_script(_interface.get_script_editor().get_current_script())
|
||||
|
||||
|
||||
func set_plugin(value):
|
||||
_gut_plugin = value
|
||||
|
||||
|
||||
func set_panel_button(value):
|
||||
_panel_button = value
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Write a file.
|
||||
# ------------------------------------------------------------------------------
|
||||
func write_file(path, content):
|
||||
var f = FileAccess.open(path, FileAccess.WRITE)
|
||||
if(f != null):
|
||||
f.store_string(content)
|
||||
f = null;
|
||||
|
||||
return FileAccess.get_open_error()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Returns the text of a file or an empty string if the file could not be opened.
|
||||
# ------------------------------------------------------------------------------
|
||||
func get_file_as_text(path):
|
||||
var to_return = ''
|
||||
var f = FileAccess.open(path, FileAccess.READ)
|
||||
if(f != null):
|
||||
to_return = f.get_as_text()
|
||||
f = null
|
||||
return to_return
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# return if_null if value is null otherwise return value
|
||||
# ------------------------------------------------------------------------------
|
||||
func nvl(value, if_null):
|
||||
if(value == null):
|
||||
return if_null
|
||||
else:
|
||||
return value
|
||||
@@ -0,0 +1,249 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://b3bostcslstem"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/GutBottomPanel.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsk32dh41b4gs" path="res://addons/gut/gui/BottomPanelShortcuts.tscn" id="2"]
|
||||
[ext_resource type="PackedScene" uid="uid://0yunjxtaa8iw" path="res://addons/gut/gui/RunAtCursor.tscn" id="3"]
|
||||
[ext_resource type="Texture2D" uid="uid://cr6tvdv0ve6cv" path="res://addons/gut/gui/play.png" id="4"]
|
||||
[ext_resource type="PackedScene" uid="uid://4gyyn12um08h" path="res://addons/gut/gui/RunResults.tscn" id="5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bqmo4dj64c7yl" path="res://addons/gut/gui/OutputText.tscn" id="6"]
|
||||
|
||||
[sub_resource type="Shortcut" id="9"]
|
||||
|
||||
[sub_resource type="Image" id="Image_18d1g"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_8u17l"]
|
||||
image = SubResource("Image_18d1g")
|
||||
|
||||
[node name="GutBottomPanel" type="Control"]
|
||||
custom_minimum_size = Vector2(250, 250)
|
||||
layout_mode = 3
|
||||
anchor_left = -0.0025866
|
||||
anchor_top = -0.00176575
|
||||
anchor_right = 0.997413
|
||||
anchor_bottom = 0.998234
|
||||
offset_left = 2.64868
|
||||
offset_top = 1.05945
|
||||
offset_right = 2.64862
|
||||
offset_bottom = 1.05945
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="layout" type="VBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="ControlBar" type="HBoxContainer" parent="layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RunAll" type="Button" parent="layout/ControlBar"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 11
|
||||
shortcut = SubResource("9")
|
||||
text = "Run All"
|
||||
icon = ExtResource("4")
|
||||
|
||||
[node name="Label" type="Label" parent="layout/ControlBar"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
text = "Current: "
|
||||
|
||||
[node name="RunAtCursor" parent="layout/ControlBar" instance=ExtResource("3")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CenterContainer2" type="CenterContainer" parent="layout/ControlBar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Sep1" type="ColorRect" parent="layout/ControlBar"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RunResultsBtn" type="Button" parent="layout/ControlBar"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="OutputBtn" type="Button" parent="layout/ControlBar"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="Settings" type="Button" parent="layout/ControlBar"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="Sep2" type="ColorRect" parent="layout/ControlBar"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Shortcuts" type="Button" parent="layout/ControlBar"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 11
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="RSplit" type="HSplitContainer" parent="layout"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="CResults" type="VBoxContainer" parent="layout/RSplit"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="ControlBar" type="HBoxContainer" parent="layout/RSplit/CResults"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sep2" type="ColorRect" parent="layout/RSplit/CResults/ControlBar"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Light3D" type="Control" parent="layout/RSplit/CResults/ControlBar"]
|
||||
custom_minimum_size = Vector2(30, 30)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Passing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Passing"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
|
||||
layout_mode = 2
|
||||
text = "Passing"
|
||||
|
||||
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
|
||||
layout_mode = 2
|
||||
text = "---"
|
||||
|
||||
[node name="Failing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Failing"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
|
||||
layout_mode = 2
|
||||
text = "Failing"
|
||||
|
||||
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
|
||||
layout_mode = 2
|
||||
text = "---"
|
||||
|
||||
[node name="Pending" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Pending"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
|
||||
layout_mode = 2
|
||||
text = "Pending"
|
||||
|
||||
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
|
||||
layout_mode = 2
|
||||
text = "---"
|
||||
|
||||
[node name="Orphans" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Orphans"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
|
||||
layout_mode = 2
|
||||
text = "Orphans"
|
||||
|
||||
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
|
||||
layout_mode = 2
|
||||
text = "---"
|
||||
|
||||
[node name="Errors" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Errors"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
|
||||
layout_mode = 2
|
||||
text = "Errors"
|
||||
|
||||
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
|
||||
layout_mode = 2
|
||||
text = "---"
|
||||
|
||||
[node name="Warnings" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Warnings"]
|
||||
custom_minimum_size = Vector2(1, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
|
||||
layout_mode = 2
|
||||
text = "Warnings"
|
||||
|
||||
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
|
||||
layout_mode = 2
|
||||
text = "---"
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="layout/RSplit/CResults/ControlBar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TabBar" type="HSplitContainer" parent="layout/RSplit/CResults"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="RunResults" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("5")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="OutputText" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("6")]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="sc" type="ScrollContainer" parent="layout/RSplit"]
|
||||
custom_minimum_size = Vector2(500, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Settings" type="VBoxContainer" parent="layout/RSplit/sc"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="BottomPanelShortcuts" parent="." instance=ExtResource("2")]
|
||||
visible = false
|
||||
|
||||
[connection signal="pressed" from="layout/ControlBar/RunAll" to="." method="_on_RunAll_pressed"]
|
||||
[connection signal="run_tests" from="layout/ControlBar/RunAtCursor" to="." method="_on_RunAtCursor_run_tests"]
|
||||
[connection signal="pressed" from="layout/ControlBar/RunResultsBtn" to="." method="_on_RunResultsBtn_pressed"]
|
||||
[connection signal="pressed" from="layout/ControlBar/OutputBtn" to="." method="_on_OutputBtn_pressed"]
|
||||
[connection signal="pressed" from="layout/ControlBar/Settings" to="." method="_on_Settings_pressed"]
|
||||
[connection signal="pressed" from="layout/ControlBar/Shortcuts" to="." method="_on_Shortcuts_pressed"]
|
||||
[connection signal="draw" from="layout/RSplit/CResults/ControlBar/Light3D" to="." method="_on_Light_draw"]
|
||||
[connection signal="visibility_changed" from="BottomPanelShortcuts" to="." method="_on_bottom_panel_shortcuts_visibility_changed"]
|
||||
121
samples/client/petstore/gdscript/addons/gut/gui/GutRunner.gd
Normal file
121
samples/client/petstore/gdscript/addons/gut/gui/GutRunner.gd
Normal file
@@ -0,0 +1,121 @@
|
||||
extends Node2D
|
||||
|
||||
var Gut = load('res://addons/gut/gut.gd')
|
||||
var ResultExporter = load('res://addons/gut/result_exporter.gd')
|
||||
var GutConfig = load('res://addons/gut/gut_config.gd')
|
||||
|
||||
const RUNNER_JSON_PATH = 'res://.gut_editor_config.json'
|
||||
const RESULT_FILE = 'user://.gut_editor.bbcode'
|
||||
const RESULT_JSON = 'user://.gut_editor.json'
|
||||
|
||||
var _gut_config = null
|
||||
var _gut = null;
|
||||
var _wrote_results = false
|
||||
# Flag for when this is being used at the command line. Otherwise it is
|
||||
# assumed this is being used by the panel and being launched with
|
||||
# play_custom_scene
|
||||
var _cmdln_mode = false
|
||||
|
||||
@onready var _gut_layer = $GutLayer
|
||||
@onready var _gui = $GutLayer/GutScene
|
||||
|
||||
var auto_run_tests = true
|
||||
|
||||
func _ready():
|
||||
if(_gut_config == null):
|
||||
_gut_config = GutConfig.new()
|
||||
_gut_config.load_panel_options(RUNNER_JSON_PATH)
|
||||
|
||||
# The command line will call run_tests on its own. When used from the panel
|
||||
# we have to kick off the tests ourselves b/c there's no way I know of to
|
||||
# interact with the scene that was run via play_custom_scene.
|
||||
if(!_cmdln_mode and auto_run_tests):
|
||||
call_deferred('run_tests')
|
||||
|
||||
|
||||
func run_tests(show_gui=true):
|
||||
|
||||
if(_gut == null):
|
||||
get_gut()
|
||||
|
||||
_setup_gui(show_gui)
|
||||
|
||||
_gut.add_children_to = self
|
||||
if(_gut_config.options.gut_on_top):
|
||||
_gut_layer.add_child(_gut)
|
||||
else:
|
||||
add_child(_gut)
|
||||
|
||||
if(!_cmdln_mode):
|
||||
_gut.end_run.connect(_on_tests_finished.bind(_gut_config.options.should_exit, _gut_config.options.should_exit_on_success))
|
||||
|
||||
_gut_config.config_gut(_gut)
|
||||
var run_rest_of_scripts = _gut_config.options.unit_test_name == ''
|
||||
|
||||
_gut.test_scripts(run_rest_of_scripts)
|
||||
|
||||
|
||||
func _setup_gui(show_gui):
|
||||
if(show_gui):
|
||||
_gui.gut = _gut
|
||||
var printer = _gut.logger.get_printer('gui')
|
||||
printer.set_textbox(_gui.get_textbox())
|
||||
else:
|
||||
_gut.logger.disable_printer('gui', true)
|
||||
_gui.visible = false
|
||||
|
||||
var opts = _gut_config.options
|
||||
_gui.set_font_size(opts.font_size)
|
||||
_gui.set_font(opts.font_name)
|
||||
if(opts.font_color != null and opts.font_color.is_valid_html_color()):
|
||||
_gui.set_default_font_color(Color(opts.font_color))
|
||||
if(opts.background_color != null and opts.background_color.is_valid_html_color()):
|
||||
_gui.set_background_color(Color(opts.background_color))
|
||||
|
||||
_gui.set_opacity(min(1.0, float(opts.opacity) / 100))
|
||||
# if(opts.should_maximize):
|
||||
# _tester.maximize()
|
||||
_gui.use_compact_mode(opts.compact_mode)
|
||||
|
||||
|
||||
|
||||
func _write_results():
|
||||
var content = _gui.get_textbox().get_parsed_text() #_gut.logger.get_gui_bbcode()
|
||||
var f = FileAccess.open(RESULT_FILE, FileAccess.WRITE)
|
||||
if(f != null):
|
||||
f.store_string(content)
|
||||
f = null # closes file
|
||||
else:
|
||||
push_error('Could not save bbcode, result = ', FileAccess.get_open_error())
|
||||
|
||||
var exporter = ResultExporter.new()
|
||||
var f_result = exporter.write_json_file(_gut, RESULT_JSON)
|
||||
_wrote_results = true
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
if(!_wrote_results and !_cmdln_mode):
|
||||
_write_results()
|
||||
|
||||
|
||||
func _on_tests_finished(should_exit, should_exit_on_success):
|
||||
_write_results()
|
||||
|
||||
if(should_exit):
|
||||
get_tree().quit()
|
||||
elif(should_exit_on_success and _gut.get_fail_count() == 0):
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func get_gut():
|
||||
if(_gut == null):
|
||||
_gut = Gut.new()
|
||||
return _gut
|
||||
|
||||
|
||||
func set_gut_config(which):
|
||||
_gut_config = which
|
||||
|
||||
|
||||
func set_cmdln_mode(is_it):
|
||||
_cmdln_mode = is_it
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bqy3ikt6vu4b5"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/GutRunner.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://m28heqtswbuq" path="res://addons/gut/GutScene.tscn" id="2_6ruxb"]
|
||||
|
||||
[node name="GutRunner" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="GutLayer" type="CanvasLayer" parent="."]
|
||||
layer = 128
|
||||
|
||||
[node name="GutScene" parent="GutLayer" instance=ExtResource("2_6ruxb")]
|
||||
File diff suppressed because one or more lines are too long
161
samples/client/petstore/gdscript/addons/gut/gui/MinGui.tscn
Normal file
161
samples/client/petstore/gdscript/addons/gut/gui/MinGui.tscn
Normal file
@@ -0,0 +1,161 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cnqqdfsn80ise"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_farmq"]
|
||||
[ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="2_a2e2l"]
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/gut_gui.gd" id="2_eokrf"]
|
||||
[ext_resource type="PackedScene" uid="uid://bvrqqgjpyouse" path="res://addons/gut/gui/ResizeHandle.tscn" id="4_xrhva"]
|
||||
|
||||
[node name="Min" type="Panel"]
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(280, 145)
|
||||
offset_right = 280.0
|
||||
offset_bottom = 145.0
|
||||
theme = ExtResource("1_farmq")
|
||||
script = ExtResource("2_eokrf")
|
||||
|
||||
[node name="MainBox" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="TitleBar" type="Panel" parent="MainBox"]
|
||||
custom_minimum_size = Vector2(0, 25)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleBox" type="HBoxContainer" parent="MainBox/TitleBar"]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = 2.0
|
||||
offset_bottom = 3.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="Spacer1" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Title" type="Label" parent="MainBox/TitleBar/TitleBox"]
|
||||
layout_mode = 2
|
||||
text = "Title"
|
||||
|
||||
[node name="Spacer2" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TimeLabel" type="Label" parent="MainBox/TitleBar/TitleBox"]
|
||||
layout_mode = 2
|
||||
text = "0.000s"
|
||||
|
||||
[node name="Body" type="HBoxContainer" parent="MainBox"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="LeftMargin" type="CenterContainer" parent="MainBox/Body"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BodyRows" type="VBoxContainer" parent="MainBox/Body"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ProgressBars" type="HBoxContainer" parent="MainBox/Body/BodyRows"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/Body/BodyRows/ProgressBars"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "T:"
|
||||
|
||||
[node name="ProgressTest" type="ProgressBar" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
value = 25.0
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="MainBox/Body/BodyRows/ProgressBars"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = "S:"
|
||||
|
||||
[node name="ProgressScript" type="ProgressBar" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
value = 75.0
|
||||
|
||||
[node name="PathDisplay" type="VBoxContainer" parent="MainBox/Body/BodyRows"]
|
||||
clip_contents = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Path" type="Label" parent="MainBox/Body/BodyRows/PathDisplay"]
|
||||
layout_mode = 2
|
||||
theme_override_fonts/font = ExtResource("2_a2e2l")
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "res://test/integration/whatever"
|
||||
clip_text = true
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/Body/BodyRows/PathDisplay"]
|
||||
clip_contents = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="S3" type="CenterContainer" parent="MainBox/Body/BodyRows/PathDisplay/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="File" type="Label" parent="MainBox/Body/BodyRows/PathDisplay/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_fonts/font = ExtResource("2_a2e2l")
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "test_this_thing.gd"
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[node name="Footer" type="HBoxContainer" parent="MainBox/Body/BodyRows"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HandleLeft" parent="MainBox/Body/BodyRows/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_xrhva")]
|
||||
layout_mode = 2
|
||||
orientation = 0
|
||||
resize_control = NodePath("../../../../..")
|
||||
vertical_resize = false
|
||||
|
||||
[node name="SwitchModes" type="Button" parent="MainBox/Body/BodyRows/Footer"]
|
||||
layout_mode = 2
|
||||
text = "Expand"
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="MainBox/Body/BodyRows/Footer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Continue" type="Button" parent="MainBox/Body/BodyRows/Footer"]
|
||||
layout_mode = 2
|
||||
text = "Continue
|
||||
"
|
||||
|
||||
[node name="HandleRight" parent="MainBox/Body/BodyRows/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_xrhva")]
|
||||
layout_mode = 2
|
||||
resize_control = NodePath("../../../../..")
|
||||
vertical_resize = false
|
||||
|
||||
[node name="RightMargin" type="CenterContainer" parent="MainBox/Body"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="MainBox"]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 2)
|
||||
layout_mode = 2
|
||||
216
samples/client/petstore/gdscript/addons/gut/gui/NormalGui.tscn
Normal file
216
samples/client/petstore/gdscript/addons/gut/gui/NormalGui.tscn
Normal file
@@ -0,0 +1,216 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://duxblir3vu8x7"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_5hlsm"]
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/gut_gui.gd" id="2_fue6q"]
|
||||
[ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="2_u5uc1"]
|
||||
[ext_resource type="PackedScene" uid="uid://bvrqqgjpyouse" path="res://addons/gut/gui/ResizeHandle.tscn" id="4_2r8a8"]
|
||||
|
||||
[node name="Large" type="Panel"]
|
||||
custom_minimum_size = Vector2(500, 150)
|
||||
offset_right = 632.0
|
||||
offset_bottom = 260.0
|
||||
theme = ExtResource("1_5hlsm")
|
||||
script = ExtResource("2_fue6q")
|
||||
|
||||
[node name="MainBox" type="VBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="TitleBar" type="Panel" parent="MainBox"]
|
||||
custom_minimum_size = Vector2(0, 25)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleBox" type="HBoxContainer" parent="MainBox/TitleBar"]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = 2.0
|
||||
offset_bottom = 3.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="Spacer1" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Title" type="Label" parent="MainBox/TitleBar/TitleBox"]
|
||||
layout_mode = 2
|
||||
text = "Title"
|
||||
|
||||
[node name="Spacer2" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TimeLabel" type="Label" parent="MainBox/TitleBar/TitleBox"]
|
||||
custom_minimum_size = Vector2(90, 0)
|
||||
layout_mode = 2
|
||||
text = "999.999s"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MainBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="OutputBG" type="ColorRect" parent="MainBox/HBoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
color = Color(0.0745098, 0.0705882, 0.0784314, 1)
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG"]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="S2" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TestOutput" type="RichTextLabel" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
focus_mode = 2
|
||||
bbcode_enabled = true
|
||||
scroll_following = true
|
||||
autowrap_mode = 0
|
||||
selection_enabled = true
|
||||
|
||||
[node name="S1" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ControlBox" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="S1" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ProgressBars" type="VBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TestBox" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox"]
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Tests"
|
||||
|
||||
[node name="ProgressTest" type="ProgressBar" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
value = 25.0
|
||||
|
||||
[node name="ScriptBox" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox"]
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Scripts"
|
||||
|
||||
[node name="ProgressScript" type="ProgressBar" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
value = 75.0
|
||||
|
||||
[node name="PathDisplay" type="VBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Path" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 6
|
||||
theme_override_fonts/font = ExtResource("2_u5uc1")
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "res://test/integration/whatever"
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="S3" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="File" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_fonts/font = ExtResource("2_u5uc1")
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "test_this_thing.gd"
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[node name="Spacer1" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 10
|
||||
|
||||
[node name="Continue" type="Button" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
text = "Continue
|
||||
"
|
||||
|
||||
[node name="S3" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
|
||||
custom_minimum_size = Vector2(5, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BottomPad" type="CenterContainer" parent="MainBox"]
|
||||
custom_minimum_size = Vector2(0, 5)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Footer" type="HBoxContainer" parent="MainBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SidePad1" type="CenterContainer" parent="MainBox/Footer"]
|
||||
custom_minimum_size = Vector2(2, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ResizeHandle3" parent="MainBox/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_2r8a8")]
|
||||
custom_minimum_size = Vector2(25, 25)
|
||||
layout_mode = 2
|
||||
orientation = 0
|
||||
resize_control = NodePath("../../..")
|
||||
vertical_resize = null
|
||||
|
||||
[node name="SwitchModes" type="Button" parent="MainBox/Footer"]
|
||||
layout_mode = 2
|
||||
text = "Compact
|
||||
"
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="MainBox/Footer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ResizeHandle2" parent="MainBox/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_2r8a8")]
|
||||
custom_minimum_size = Vector2(25, 25)
|
||||
layout_mode = 2
|
||||
orientation = 1
|
||||
resize_control = NodePath("../../..")
|
||||
vertical_resize = null
|
||||
|
||||
[node name="SidePad2" type="CenterContainer" parent="MainBox/Footer"]
|
||||
custom_minimum_size = Vector2(2, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BottomPad2" type="CenterContainer" parent="MainBox"]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 2)
|
||||
layout_mode = 2
|
||||
341
samples/client/petstore/gdscript/addons/gut/gui/OutputText.gd
Normal file
341
samples/client/petstore/gdscript/addons/gut/gui/OutputText.gd
Normal file
@@ -0,0 +1,341 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
# ##############################################################################
|
||||
# Keeps search results from the TextEdit
|
||||
# ##############################################################################
|
||||
class TextEditSearcher:
|
||||
var te : TextEdit
|
||||
var _last_term = ''
|
||||
var _last_pos = Vector2(-1, -1)
|
||||
var _ignore_caret_change = false
|
||||
|
||||
func set_text_edit(which):
|
||||
te = which
|
||||
te.caret_changed.connect(_on_caret_changed)
|
||||
|
||||
|
||||
func _on_caret_changed():
|
||||
if(_ignore_caret_change):
|
||||
_ignore_caret_change = false
|
||||
else:
|
||||
_last_pos = _get_caret();
|
||||
|
||||
|
||||
func _get_caret():
|
||||
return Vector2(te.get_caret_column(), te.get_caret_line())
|
||||
|
||||
|
||||
func _set_caret_and_sel(pos, len):
|
||||
te.set_caret_line(pos.y)
|
||||
te.set_caret_column(pos.x)
|
||||
if(len > 0):
|
||||
te.select(pos.y, pos.x, pos.y, pos.x + len)
|
||||
|
||||
|
||||
func _find(term, search_flags):
|
||||
var pos = _get_caret()
|
||||
if(term == _last_term):
|
||||
if(search_flags == 0):
|
||||
pos = _last_pos
|
||||
pos.x += 1
|
||||
else:
|
||||
pos = _last_pos
|
||||
pos.x -= 1
|
||||
|
||||
var result = te.search(term, search_flags, pos.y, pos.x)
|
||||
# print('searching from ', pos, ' for "', term, '" = ', result)
|
||||
if(result.y != -1):
|
||||
_ignore_caret_change = true
|
||||
_set_caret_and_sel(result, term.length())
|
||||
_last_pos = result
|
||||
|
||||
_last_term = term
|
||||
|
||||
func find_next(term):
|
||||
_find(term, 0)
|
||||
|
||||
func find_prev(term):
|
||||
_find(term, te.SEARCH_BACKWARDS)
|
||||
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# Start OutputText control code
|
||||
# ##############################################################################
|
||||
@onready var _ctrls = {
|
||||
output = $Output,
|
||||
|
||||
copy_button = $Toolbar/CopyButton,
|
||||
use_colors = $Toolbar/UseColors,
|
||||
clear_button = $Toolbar/ClearButton,
|
||||
word_wrap = $Toolbar/WordWrap,
|
||||
show_search = $Toolbar/ShowSearch,
|
||||
caret_position = $Toolbar/LblPosition,
|
||||
|
||||
search_bar = {
|
||||
bar = $Search,
|
||||
search_term = $Search/SearchTerm,
|
||||
}
|
||||
}
|
||||
|
||||
var _sr = TextEditSearcher.new()
|
||||
var _highlighter : CodeHighlighter
|
||||
|
||||
# Automatically used when running the OutputText scene from the editor. Changes
|
||||
# to this method only affect test-running the control through the editor.
|
||||
func _test_running_setup():
|
||||
_ctrls.use_colors.text = 'use colors'
|
||||
_ctrls.show_search.text = 'search'
|
||||
_ctrls.word_wrap.text = 'ww'
|
||||
|
||||
set_all_fonts("CourierPrime")
|
||||
set_font_size(5)
|
||||
# print(_ctrls.output.get_theme_font_size("normal_font"))
|
||||
_ctrls.output.queue_redraw()
|
||||
|
||||
load_file('user://.gut_editor.bbcode')
|
||||
await get_tree().process_frame
|
||||
|
||||
show_search(true)
|
||||
_ctrls.output.set_caret_line(0)
|
||||
_ctrls.output.scroll_vertical = 0
|
||||
|
||||
_ctrls.output.caret_changed.connect(_on_caret_changed)
|
||||
|
||||
|
||||
func _on_caret_changed():
|
||||
var txt = str("line:",_ctrls.output.get_caret_line(), ' col:', _ctrls.output.get_caret_column())
|
||||
_ctrls.caret_position.text = str(txt)
|
||||
|
||||
|
||||
func _ready():
|
||||
_sr.set_text_edit(_ctrls.output)
|
||||
_ctrls.use_colors.icon = get_theme_icon('RichTextEffect', 'EditorIcons')
|
||||
_ctrls.show_search.icon = get_theme_icon('Search', 'EditorIcons')
|
||||
_ctrls.word_wrap.icon = get_theme_icon('Loop', 'EditorIcons')
|
||||
|
||||
_setup_colors()
|
||||
_ctrls.use_colors.button_pressed = true
|
||||
_use_highlighting(true)
|
||||
|
||||
if(get_parent() == get_tree().root):
|
||||
_test_running_setup()
|
||||
|
||||
# ------------------
|
||||
# Private
|
||||
# ------------------
|
||||
|
||||
# Call this after changes in colors and the like to get them to apply. reloads
|
||||
# the text of the output control.
|
||||
func _refresh_output():
|
||||
var orig_pos = _ctrls.output.scroll_vertical
|
||||
var text = _ctrls.output.text
|
||||
|
||||
_ctrls.output.text = text
|
||||
_ctrls.output.scroll_vertical = orig_pos
|
||||
|
||||
|
||||
func _create_highlighter(default_color=Color(1, 1, 1, 1)):
|
||||
var to_return = CodeHighlighter.new()
|
||||
|
||||
to_return.function_color = default_color
|
||||
to_return.number_color = default_color
|
||||
to_return.symbol_color = default_color
|
||||
to_return.member_variable_color = default_color
|
||||
|
||||
var keywords = [
|
||||
['Failed', Color.RED],
|
||||
['Passed', Color.GREEN],
|
||||
['Pending', Color.YELLOW],
|
||||
['Orphans', Color.YELLOW],
|
||||
['WARNING', Color.YELLOW],
|
||||
['ERROR', Color.RED]
|
||||
]
|
||||
|
||||
for keyword in keywords:
|
||||
to_return.add_keyword_color(keyword[0], keyword[1])
|
||||
|
||||
return to_return
|
||||
|
||||
|
||||
func _setup_colors():
|
||||
_ctrls.output.clear()
|
||||
|
||||
var f_color = null
|
||||
if (_ctrls.output.theme == null) :
|
||||
f_color = get_theme_color("font_color")
|
||||
else :
|
||||
f_color = _ctrls.output.theme.font_color
|
||||
|
||||
_highlighter = _create_highlighter()
|
||||
_ctrls.output.queue_redraw()
|
||||
|
||||
|
||||
func _set_font(font_name, custom_name):
|
||||
var rtl = _ctrls.output
|
||||
if(font_name == null):
|
||||
rtl.add_theme_font_override(custom_name, null)
|
||||
else:
|
||||
var dyn_font = FontFile.new()
|
||||
dyn_font.load_dynamic_font('res://addons/gut/fonts/' + font_name + '.ttf')
|
||||
rtl.add_theme_font_override(custom_name, dyn_font)
|
||||
|
||||
|
||||
func _use_highlighting(should):
|
||||
if(should):
|
||||
_ctrls.output.syntax_highlighter = _highlighter
|
||||
else:
|
||||
_ctrls.output.syntax_highlighter = null
|
||||
_refresh_output()
|
||||
|
||||
# ------------------
|
||||
# Events
|
||||
# ------------------
|
||||
func _on_CopyButton_pressed():
|
||||
copy_to_clipboard()
|
||||
|
||||
|
||||
func _on_UseColors_pressed():
|
||||
_use_highlighting(_ctrls.use_colors.button_pressed)
|
||||
|
||||
|
||||
func _on_ClearButton_pressed():
|
||||
clear()
|
||||
|
||||
|
||||
func _on_ShowSearch_pressed():
|
||||
show_search(_ctrls.show_search.button_pressed)
|
||||
|
||||
|
||||
func _on_SearchTerm_focus_entered():
|
||||
_ctrls.search_bar.search_term.call_deferred('select_all')
|
||||
|
||||
func _on_SearchNext_pressed():
|
||||
_sr.find_next(_ctrls.search_bar.search_term.text)
|
||||
|
||||
|
||||
func _on_SearchPrev_pressed():
|
||||
_sr.find_prev(_ctrls.search_bar.search_term.text)
|
||||
|
||||
|
||||
func _on_SearchTerm_text_changed(new_text):
|
||||
if(new_text == ''):
|
||||
_ctrls.output.deselect()
|
||||
else:
|
||||
_sr.find_next(new_text)
|
||||
|
||||
|
||||
func _on_SearchTerm_text_entered(new_text):
|
||||
if(Input.is_physical_key_pressed(KEY_SHIFT)):
|
||||
_sr.find_prev(new_text)
|
||||
else:
|
||||
_sr.find_next(new_text)
|
||||
|
||||
|
||||
func _on_SearchTerm_gui_input(event):
|
||||
if(event is InputEventKey and !event.pressed and event.keycode == KEY_ESCAPE):
|
||||
show_search(false)
|
||||
|
||||
|
||||
func _on_WordWrap_pressed():
|
||||
if(_ctrls.word_wrap.button_pressed):
|
||||
_ctrls.output.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
|
||||
else:
|
||||
_ctrls.output.wrap_mode = TextEdit.LINE_WRAPPING_NONE
|
||||
|
||||
_ctrls.output.queue_redraw()
|
||||
|
||||
# ------------------
|
||||
# Public
|
||||
# ------------------
|
||||
func show_search(should):
|
||||
_ctrls.search_bar.bar.visible = should
|
||||
if(should):
|
||||
_ctrls.search_bar.search_term.grab_focus()
|
||||
_ctrls.search_bar.search_term.select_all()
|
||||
_ctrls.show_search.button_pressed = should
|
||||
|
||||
|
||||
func search(text, start_pos, highlight=true):
|
||||
return _sr.find_next(text)
|
||||
|
||||
|
||||
func copy_to_clipboard():
|
||||
var selected = _ctrls.output.get_selected_text()
|
||||
if(selected != ''):
|
||||
DisplayServer.clipboard_set(selected)
|
||||
else:
|
||||
DisplayServer.clipboard_set(_ctrls.output.text)
|
||||
|
||||
|
||||
func clear():
|
||||
_ctrls.output.text = ''
|
||||
|
||||
|
||||
func set_all_fonts(base_name):
|
||||
if(base_name == 'Default'):
|
||||
_set_font(null, 'font')
|
||||
_set_font(null, 'normal_font')
|
||||
_set_font(null, 'bold_font')
|
||||
_set_font(null, 'italics_font')
|
||||
_set_font(null, 'bold_italics_font')
|
||||
else:
|
||||
_set_font(base_name + '-Regular', 'font')
|
||||
_set_font(base_name + '-Regular', 'normal_font')
|
||||
_set_font(base_name + '-Bold', 'bold_font')
|
||||
_set_font(base_name + '-Italic', 'italics_font')
|
||||
_set_font(base_name + '-BoldItalic', 'bold_italics_font')
|
||||
|
||||
|
||||
func set_font_size(new_size):
|
||||
return # this isn't working.
|
||||
var rtl = _ctrls.output
|
||||
# rtl.add_theme_font_size_override("font", new_size)
|
||||
# rtl.add_theme_font_size_override("normal_font", new_size)
|
||||
# rtl.add_theme_font_size_override("bold_font", new_size)
|
||||
# rtl.add_theme_font_size_override("italics_font", new_size)
|
||||
# rtl.add_theme_font_size_override("bold_italics_font", new_size)
|
||||
rtl.set("theme_override_font_sizes/size", new_size)
|
||||
# print(rtl.get("theme_override_font_sizes/size"))
|
||||
|
||||
# if(rtl.get('custom_fonts/font') != null):
|
||||
# rtl.get('custom_fonts/font').size = new_size
|
||||
# rtl.get('custom_fonts/bold_italics_font').size = new_size
|
||||
# rtl.get('custom_fonts/bold_font').size = new_size
|
||||
# rtl.get('custom_fonts/italics_font').size = new_size
|
||||
# rtl.get('custom_fonts/normal_font').size = new_size
|
||||
|
||||
|
||||
func set_use_colors(value):
|
||||
pass
|
||||
|
||||
|
||||
func get_use_colors():
|
||||
return false;
|
||||
|
||||
|
||||
func get_rich_text_edit():
|
||||
return _ctrls.output
|
||||
|
||||
|
||||
func load_file(path):
|
||||
var f = FileAccess.open(path, FileAccess.READ)
|
||||
if(f == null):
|
||||
return
|
||||
|
||||
var t = f.get_as_text()
|
||||
f = null # closes file
|
||||
_ctrls.output.text = t
|
||||
_ctrls.output.scroll_vertical = _ctrls.output.get_line_count()
|
||||
_ctrls.output.set_deferred('scroll_vertical', _ctrls.output.get_line_count())
|
||||
|
||||
|
||||
func add_text(text):
|
||||
if(is_inside_tree()):
|
||||
_ctrls.output.text += text
|
||||
|
||||
|
||||
func scroll_to_line(line):
|
||||
_ctrls.output.scroll_vertical = line
|
||||
_ctrls.output.set_caret_line(line)
|
||||
114
samples/client/petstore/gdscript/addons/gut/gui/OutputText.tscn
Normal file
114
samples/client/petstore/gdscript/addons/gut/gui/OutputText.tscn
Normal file
@@ -0,0 +1,114 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://bqmo4dj64c7yl"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/OutputText.gd" id="1"]
|
||||
|
||||
[sub_resource type="Image" id="Image_o4jv5"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_uk57o"]
|
||||
image = SubResource("Image_o4jv5")
|
||||
|
||||
[sub_resource type="CodeHighlighter" id="CodeHighlighter_sv352"]
|
||||
number_color = Color(1, 1, 1, 1)
|
||||
symbol_color = Color(1, 1, 1, 1)
|
||||
function_color = Color(1, 1, 1, 1)
|
||||
member_variable_color = Color(1, 1, 1, 1)
|
||||
keyword_colors = {
|
||||
"ERROR": Color(1, 0, 0, 1),
|
||||
"Failed": Color(1, 0, 0, 1),
|
||||
"Orphans": Color(1, 1, 0, 1),
|
||||
"Passed": Color(0, 1, 0, 1),
|
||||
"Pending": Color(1, 1, 0, 1),
|
||||
"WARNING": Color(1, 1, 0, 1)
|
||||
}
|
||||
|
||||
[node name="OutputText" type="VBoxContainer"]
|
||||
offset_right = 862.0
|
||||
offset_bottom = 523.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Toolbar" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ShowSearch" type="Button" parent="Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Search"
|
||||
toggle_mode = true
|
||||
icon = SubResource("ImageTexture_uk57o")
|
||||
|
||||
[node name="UseColors" type="Button" parent="Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Colorized Text"
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
icon = SubResource("ImageTexture_uk57o")
|
||||
|
||||
[node name="WordWrap" type="Button" parent="Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Word Wrap"
|
||||
toggle_mode = true
|
||||
icon = SubResource("ImageTexture_uk57o")
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Toolbar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="LblPosition" type="Label" parent="Toolbar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CopyButton" type="Button" parent="Toolbar"]
|
||||
layout_mode = 2
|
||||
text = " Copy "
|
||||
|
||||
[node name="ClearButton" type="Button" parent="Toolbar"]
|
||||
layout_mode = 2
|
||||
text = " Clear "
|
||||
|
||||
[node name="Output" type="TextEdit" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
deselect_on_focus_loss_enabled = false
|
||||
virtual_keyboard_enabled = false
|
||||
middle_mouse_paste_enabled = false
|
||||
highlight_all_occurrences = true
|
||||
highlight_current_line = true
|
||||
syntax_highlighter = SubResource("CodeHighlighter_sv352")
|
||||
scroll_smooth = true
|
||||
|
||||
[node name="Search" type="HBoxContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SearchTerm" type="LineEdit" parent="Search"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SearchNext" type="Button" parent="Search"]
|
||||
layout_mode = 2
|
||||
text = "Next"
|
||||
|
||||
[node name="SearchPrev" type="Button" parent="Search"]
|
||||
layout_mode = 2
|
||||
text = "Prev"
|
||||
|
||||
[connection signal="pressed" from="Toolbar/ShowSearch" to="." method="_on_ShowSearch_pressed"]
|
||||
[connection signal="pressed" from="Toolbar/UseColors" to="." method="_on_UseColors_pressed"]
|
||||
[connection signal="pressed" from="Toolbar/WordWrap" to="." method="_on_WordWrap_pressed"]
|
||||
[connection signal="pressed" from="Toolbar/CopyButton" to="." method="_on_CopyButton_pressed"]
|
||||
[connection signal="pressed" from="Toolbar/ClearButton" to="." method="_on_ClearButton_pressed"]
|
||||
[connection signal="focus_entered" from="Search/SearchTerm" to="." method="_on_SearchTerm_focus_entered"]
|
||||
[connection signal="gui_input" from="Search/SearchTerm" to="." method="_on_SearchTerm_gui_input"]
|
||||
[connection signal="text_changed" from="Search/SearchTerm" to="." method="_on_SearchTerm_text_changed"]
|
||||
[connection signal="text_submitted" from="Search/SearchTerm" to="." method="_on_SearchTerm_text_entered"]
|
||||
[connection signal="pressed" from="Search/SearchNext" to="." method="_on_SearchNext_pressed"]
|
||||
[connection signal="pressed" from="Search/SearchPrev" to="." method="_on_SearchPrev_pressed"]
|
||||
108
samples/client/petstore/gdscript/addons/gut/gui/ResizeHandle.gd
Normal file
108
samples/client/petstore/gdscript/addons/gut/gui/ResizeHandle.gd
Normal file
@@ -0,0 +1,108 @@
|
||||
@tool
|
||||
extends ColorRect
|
||||
# #############################################################################
|
||||
# Resize Handle control. Place onto a control. Set the orientation, then
|
||||
# set the control that this should resize. Then you can resize the control
|
||||
# by dragging this thing around. It's pretty neat.
|
||||
# #############################################################################
|
||||
enum ORIENTATION {
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
@export var orientation := ORIENTATION.RIGHT :
|
||||
get: return orientation
|
||||
set(val):
|
||||
orientation = val
|
||||
queue_redraw()
|
||||
@export var resize_control : Control = null
|
||||
@export var vertical_resize := true
|
||||
|
||||
var _line_width = .5
|
||||
var _line_color = Color(.4, .4, .4)
|
||||
var _active_line_color = Color(.3, .3, .3)
|
||||
var _invalid_line_color = Color(1, 0, 0)
|
||||
|
||||
var _grab_margin = 2
|
||||
var _line_space = 3
|
||||
var _num_lines = 8
|
||||
|
||||
var _mouse_down = false
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
|
||||
|
||||
func _draw():
|
||||
var c = _line_color
|
||||
if(resize_control == null):
|
||||
c = _invalid_line_color
|
||||
elif(_mouse_down):
|
||||
c = _active_line_color
|
||||
|
||||
if(orientation == ORIENTATION.LEFT):
|
||||
_draw_resize_handle_left(c)
|
||||
else:
|
||||
_draw_resize_handle_right(c)
|
||||
|
||||
|
||||
func _gui_input(event):
|
||||
if(resize_control == null):
|
||||
return
|
||||
|
||||
if(orientation == ORIENTATION.LEFT):
|
||||
_handle_left_input(event)
|
||||
else:
|
||||
_handle_right_input(event)
|
||||
|
||||
|
||||
# Draw the lines in the corner to show where you can
|
||||
# drag to resize the dialog
|
||||
func _draw_resize_handle_right(color):
|
||||
var br = size
|
||||
|
||||
for i in range(_num_lines):
|
||||
var start = br - Vector2(i * _line_space, 0)
|
||||
var end = br - Vector2(0, i * _line_space)
|
||||
draw_line(start, end, color, _line_width, true)
|
||||
|
||||
|
||||
func _draw_resize_handle_left(color):
|
||||
var bl = Vector2(0, size.y)
|
||||
|
||||
for i in range(_num_lines):
|
||||
var start = bl + Vector2(i * _line_space, 0)
|
||||
var end = bl - Vector2(0, i * _line_space)
|
||||
draw_line(start, end, color, _line_width, true)
|
||||
|
||||
|
||||
func _handle_right_input(event : InputEvent):
|
||||
if(event is InputEventMouseMotion):
|
||||
if(_mouse_down and
|
||||
event.global_position.x > 0 and
|
||||
event.global_position.y < DisplayServer.window_get_size().y):
|
||||
|
||||
if(vertical_resize):
|
||||
resize_control.size.y += event.relative.y
|
||||
resize_control.size.x += event.relative.x
|
||||
elif(event is InputEventMouseButton):
|
||||
if(event.button_index == MOUSE_BUTTON_LEFT):
|
||||
_mouse_down = event.pressed
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _handle_left_input(event : InputEvent):
|
||||
if(event is InputEventMouseMotion):
|
||||
if(_mouse_down and
|
||||
event.global_position.x > 0 and
|
||||
event.global_position.y < DisplayServer.window_get_size().y):
|
||||
|
||||
var start_size = resize_control.size
|
||||
resize_control.size.x -= event.relative.x
|
||||
if(resize_control.size.x != start_size.x):
|
||||
resize_control.global_position.x += event.relative.x
|
||||
|
||||
if(vertical_resize):
|
||||
resize_control.size.y += event.relative.y
|
||||
elif(event is InputEventMouseButton):
|
||||
if(event.button_index == MOUSE_BUTTON_LEFT):
|
||||
_mouse_down = event.pressed
|
||||
queue_redraw()
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bvrqqgjpyouse"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/ResizeHandle.gd" id="1_oi5ed"]
|
||||
|
||||
[node name="ResizeHandle" type="ColorRect"]
|
||||
custom_minimum_size = Vector2(20, 20)
|
||||
color = Color(1, 1, 1, 0)
|
||||
script = ExtResource("1_oi5ed")
|
||||
348
samples/client/petstore/gdscript/addons/gut/gui/ResultsTree.gd
Normal file
348
samples/client/petstore/gdscript/addons/gut/gui/ResultsTree.gd
Normal file
@@ -0,0 +1,348 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
var _show_orphans = true
|
||||
var show_orphans = true :
|
||||
get: return _show_orphans
|
||||
set(val): _show_orphans = val
|
||||
|
||||
|
||||
var _hide_passing = true
|
||||
var hide_passing = true :
|
||||
get: return _hide_passing
|
||||
set(val): _hide_passing = val
|
||||
|
||||
|
||||
var _icons = {
|
||||
red = load('res://addons/gut/images/red.png'),
|
||||
green = load('res://addons/gut/images/green.png'),
|
||||
yellow = load('res://addons/gut/images/yellow.png'),
|
||||
}
|
||||
const _col_1_bg_color = Color(0, 0, 0, .1)
|
||||
var _max_icon_width = 10
|
||||
var _root : TreeItem
|
||||
|
||||
@onready var _ctrls = {
|
||||
tree = $Tree,
|
||||
lbl_overlay = $Tree/TextOverlay
|
||||
}
|
||||
|
||||
|
||||
signal item_selected(script_path, inner_class, test_name, line_number)
|
||||
# -------------------
|
||||
# Private
|
||||
# -------------------
|
||||
func _ready():
|
||||
_root = _ctrls.tree.create_item()
|
||||
_root = _ctrls.tree.create_item()
|
||||
_ctrls.tree.set_hide_root(true)
|
||||
_ctrls.tree.columns = 2
|
||||
_ctrls.tree.set_column_expand(0, true)
|
||||
_ctrls.tree.set_column_expand(1, false)
|
||||
_ctrls.tree.set_column_clip_content(0, true)
|
||||
|
||||
$Tree.item_selected.connect(_on_tree_item_selected)
|
||||
|
||||
if(get_parent() == get_tree().root):
|
||||
_test_running_setup()
|
||||
|
||||
func _test_running_setup():
|
||||
load_json_file('user://.gut_editor.json')
|
||||
|
||||
|
||||
func _on_tree_item_selected():
|
||||
var item = _ctrls.tree.get_selected()
|
||||
var item_meta = item.get_metadata(0)
|
||||
var item_type = null
|
||||
|
||||
# Only select the left side of the tree item, cause I like that better.
|
||||
# you can still click the right, but only the left gets highlighted.
|
||||
if(item.is_selected(1)):
|
||||
item.deselect(1)
|
||||
item.select(0)
|
||||
|
||||
if(item_meta == null):
|
||||
return
|
||||
else:
|
||||
item_type = item_meta.type
|
||||
|
||||
var script_path = '';
|
||||
var line = -1;
|
||||
var test_name = ''
|
||||
var inner_class = ''
|
||||
|
||||
if(item_type == 'test'):
|
||||
var s_item = item.get_parent()
|
||||
script_path = s_item.get_metadata(0)['path']
|
||||
inner_class = s_item.get_metadata(0)['inner_class']
|
||||
line = -1
|
||||
test_name = item.get_text(0)
|
||||
elif(item_type == 'assert'):
|
||||
var s_item = item.get_parent().get_parent()
|
||||
script_path = s_item.get_metadata(0)['path']
|
||||
inner_class = s_item.get_metadata(0)['inner_class']
|
||||
line = _get_line_number_from_assert_msg(item.get_text(0))
|
||||
test_name = item.get_parent().get_text(0)
|
||||
elif(item_type == 'script'):
|
||||
script_path = item.get_metadata(0)['path']
|
||||
if(item.get_parent() != _root):
|
||||
inner_class = item.get_text(0)
|
||||
line = -1
|
||||
test_name = ''
|
||||
else:
|
||||
return
|
||||
|
||||
item_selected.emit(script_path, inner_class, test_name, line)
|
||||
|
||||
|
||||
func _get_line_number_from_assert_msg(msg):
|
||||
var line = -1
|
||||
if(msg.find('at line') > 0):
|
||||
line = msg.split("at line")[-1].split(" ")[-1].to_int()
|
||||
return line
|
||||
|
||||
|
||||
func _get_path_and_inner_class_name_from_test_path(path):
|
||||
var to_return = {
|
||||
path = '',
|
||||
inner_class = ''
|
||||
}
|
||||
|
||||
to_return.path = path
|
||||
if !path.ends_with('.gd'):
|
||||
var loc = path.find('.gd')
|
||||
to_return.inner_class = path.split('.')[-1]
|
||||
to_return.path = path.substr(0, loc + 3)
|
||||
return to_return
|
||||
|
||||
|
||||
func _find_script_item_with_path(path):
|
||||
var items = _root.get_children()
|
||||
var to_return = null
|
||||
|
||||
var idx = 0
|
||||
while(idx < items.size() and to_return == null):
|
||||
var item = items[idx]
|
||||
if(item.get_metadata(0).path == path):
|
||||
to_return = item
|
||||
else:
|
||||
idx += 1
|
||||
|
||||
return to_return
|
||||
|
||||
|
||||
func _add_script_tree_item(script_path, script_json):
|
||||
var path_info = _get_path_and_inner_class_name_from_test_path(script_path)
|
||||
var item_text = script_path
|
||||
var parent = _root
|
||||
|
||||
if(path_info.inner_class != ''):
|
||||
parent = _find_script_item_with_path(path_info.path)
|
||||
item_text = path_info.inner_class
|
||||
if(parent == null):
|
||||
parent = _add_script_tree_item(path_info.path, {})
|
||||
|
||||
parent.get_metadata(0).inner_tests += script_json['props']['tests']
|
||||
parent.get_metadata(0).inner_passing += script_json['props']['tests']
|
||||
parent.get_metadata(0).inner_passing -= script_json['props']['failures']
|
||||
parent.get_metadata(0).inner_passing -= script_json['props']['pending']
|
||||
|
||||
var total_text = str("All ", parent.get_metadata(0).inner_tests, " passed")
|
||||
if(parent.get_metadata(0).inner_passing != parent.get_metadata(0).inner_tests):
|
||||
total_text = str(parent.get_metadata(0).inner_passing, '/', parent.get_metadata(0).inner_tests, ' passed.')
|
||||
parent.set_text(1, total_text)
|
||||
|
||||
var item = _ctrls.tree.create_item(parent)
|
||||
item.set_text(0, item_text)
|
||||
var meta = {
|
||||
"type":"script",
|
||||
"path":path_info.path,
|
||||
"inner_class":path_info.inner_class,
|
||||
"json":script_json,
|
||||
"inner_passing":0,
|
||||
"inner_tests":0
|
||||
}
|
||||
item.set_metadata(0, meta)
|
||||
item.set_custom_bg_color(1, _col_1_bg_color)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
func _add_assert_item(text, icon, parent_item):
|
||||
# print(' * adding assert')
|
||||
var assert_item = _ctrls.tree.create_item(parent_item)
|
||||
assert_item.set_icon_max_width(0, _max_icon_width)
|
||||
assert_item.set_text(0, text)
|
||||
assert_item.set_metadata(0, {"type":"assert"})
|
||||
assert_item.set_icon(0, icon)
|
||||
assert_item.set_custom_bg_color(1, _col_1_bg_color)
|
||||
|
||||
return assert_item
|
||||
|
||||
|
||||
func _add_test_tree_item(test_name, test_json, script_item):
|
||||
# print(' * adding test ', test_name)
|
||||
var no_orphans_to_show = !_show_orphans or (_show_orphans and test_json.orphans == 0)
|
||||
if(_hide_passing and test_json['status'] == 'pass' and no_orphans_to_show):
|
||||
return
|
||||
|
||||
var item = _ctrls.tree.create_item(script_item)
|
||||
var status = test_json['status']
|
||||
var meta = {"type":"test", "json":test_json}
|
||||
|
||||
item.set_text(0, test_name)
|
||||
item.set_text(1, status)
|
||||
item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)
|
||||
item.set_custom_bg_color(1, _col_1_bg_color)
|
||||
|
||||
item.set_metadata(0, meta)
|
||||
item.set_icon_max_width(0, _max_icon_width)
|
||||
|
||||
var orphan_text = 'orphans'
|
||||
if(test_json.orphans == 1):
|
||||
orphan_text = 'orphan'
|
||||
orphan_text = str(test_json.orphans, ' ', orphan_text)
|
||||
|
||||
if(status == 'pass' and no_orphans_to_show):
|
||||
item.set_icon(0, _icons.green)
|
||||
elif(status == 'pass' and !no_orphans_to_show):
|
||||
item.set_icon(0, _icons.yellow)
|
||||
item.set_text(1, orphan_text)
|
||||
elif(status == 'fail'):
|
||||
item.set_icon(0, _icons.red)
|
||||
else:
|
||||
item.set_icon(0, _icons.yellow)
|
||||
|
||||
if(!_hide_passing):
|
||||
for passing in test_json.passing:
|
||||
_add_assert_item('pass: ' + passing, _icons.green, item)
|
||||
|
||||
for failure in test_json.failing:
|
||||
_add_assert_item("fail: " + failure.replace("\n", ''), _icons.red, item)
|
||||
|
||||
for pending in test_json.pending:
|
||||
_add_assert_item("pending: " + pending.replace("\n", ''), _icons.yellow, item)
|
||||
|
||||
if(status != 'pass' and !no_orphans_to_show):
|
||||
_add_assert_item(orphan_text, _icons.yellow, item)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
func _add_script_to_tree(key, script_json):
|
||||
var tests = script_json['tests']
|
||||
var test_keys = tests.keys()
|
||||
var s_item = _add_script_tree_item(key, script_json)
|
||||
var bad_count = 0
|
||||
|
||||
for test_key in test_keys:
|
||||
var t_item = _add_test_tree_item(test_key, tests[test_key], s_item)
|
||||
if(tests[test_key].status != 'pass'):
|
||||
bad_count += 1
|
||||
elif(t_item != null):
|
||||
t_item.collapsed = true
|
||||
|
||||
if(s_item.get_children().size() == 0):
|
||||
s_item.free()
|
||||
else:
|
||||
var total_text = str('All ', test_keys.size(), ' passed')
|
||||
if(bad_count == 0):
|
||||
s_item.collapsed = true
|
||||
else:
|
||||
total_text = str(test_keys.size() - bad_count, '/', test_keys.size(), ' passed')
|
||||
s_item.set_text(1, total_text)
|
||||
|
||||
|
||||
func _free_childless_scripts():
|
||||
var items = _root.get_children()
|
||||
for item in items:
|
||||
var next_item = item.get_next()
|
||||
if(item.get_children().size() == 0):
|
||||
item.free()
|
||||
item = next_item
|
||||
|
||||
|
||||
func _show_all_passed():
|
||||
if(_root.get_children() == null):
|
||||
add_centered_text('Everything passed!')
|
||||
|
||||
|
||||
func _load_result_tree(j):
|
||||
var scripts = j['test_scripts']['scripts']
|
||||
var script_keys = scripts.keys()
|
||||
# if we made it here, the json is valid and we did something, otherwise the
|
||||
# 'nothing to see here' should be visible.
|
||||
clear_centered_text()
|
||||
|
||||
for key in script_keys:
|
||||
if(scripts[key]['props']['tests'] > 0):
|
||||
_add_script_to_tree(key, scripts[key])
|
||||
|
||||
_free_childless_scripts()
|
||||
_show_all_passed()
|
||||
|
||||
|
||||
# -------------------
|
||||
# Public
|
||||
# -------------------
|
||||
func load_json_file(path):
|
||||
var file = FileAccess.open(path, FileAccess.READ)
|
||||
var text = ''
|
||||
if(file != null):
|
||||
text = file.get_as_text()
|
||||
|
||||
if(text != ''):
|
||||
var test_json_conv = JSON.new()
|
||||
var result = test_json_conv.parse(text)
|
||||
if(result != OK):
|
||||
add_centered_text(str(path, " has invalid json in it \n",
|
||||
'Error ', result, "@", test_json_conv.get_error_line(), "\n",
|
||||
test_json_conv.get_error_message()))
|
||||
return
|
||||
|
||||
var data = test_json_conv.get_data()
|
||||
load_json_results(data)
|
||||
else:
|
||||
add_centered_text(str(path, ' was empty or does not exist.'))
|
||||
|
||||
|
||||
func load_json_results(j):
|
||||
clear()
|
||||
_load_result_tree(j)
|
||||
|
||||
|
||||
func clear():
|
||||
_ctrls.tree.clear()
|
||||
_root = _ctrls.tree.create_item()
|
||||
|
||||
|
||||
func set_summary_min_width(width):
|
||||
_ctrls.tree.set_column_custom_minimum_width(1, width)
|
||||
|
||||
|
||||
func add_centered_text(t):
|
||||
_ctrls.lbl_overlay.visible = true
|
||||
_ctrls.lbl_overlay.text = t
|
||||
|
||||
|
||||
func clear_centered_text():
|
||||
_ctrls.lbl_overlay.visible = false
|
||||
_ctrls.lbl_overlay.text = ''
|
||||
|
||||
|
||||
func collapse_all():
|
||||
set_collapsed_on_all(_root, true)
|
||||
|
||||
|
||||
func expand_all():
|
||||
set_collapsed_on_all(_root, false)
|
||||
|
||||
|
||||
func set_collapsed_on_all(item, value):
|
||||
item.set_collapsed_recursive(value)
|
||||
if(item == _root and value):
|
||||
item.set_collapsed(false)
|
||||
|
||||
|
||||
func get_selected():
|
||||
return _ctrls.tree.get_selected()
|
||||
@@ -0,0 +1,32 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dls5r5f6157nq"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/ResultsTree.gd" id="1_b4uub"]
|
||||
|
||||
[node name="ResultsTree" type="VBoxContainer"]
|
||||
custom_minimum_size = Vector2(10, 10)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_right = -70.0
|
||||
offset_bottom = -104.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_b4uub")
|
||||
|
||||
[node name="Tree" type="Tree" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
columns = 2
|
||||
hide_root = true
|
||||
|
||||
[node name="TextOverlay" type="Label" parent="Tree"]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
158
samples/client/petstore/gdscript/addons/gut/gui/RunAtCursor.gd
Normal file
158
samples/client/petstore/gdscript/addons/gut/gui/RunAtCursor.gd
Normal file
@@ -0,0 +1,158 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
|
||||
var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd')
|
||||
|
||||
@onready var _ctrls = {
|
||||
btn_script = $HBox/BtnRunScript,
|
||||
btn_inner = $HBox/BtnRunInnerClass,
|
||||
btn_method = $HBox/BtnRunMethod,
|
||||
lbl_none = $HBox/LblNoneSelected,
|
||||
arrow_1 = $HBox/Arrow1,
|
||||
arrow_2 = $HBox/Arrow2
|
||||
}
|
||||
|
||||
var _editors = null
|
||||
var _cur_editor = null
|
||||
var _last_line = -1
|
||||
var _cur_script_path = null
|
||||
var _last_info = {
|
||||
script = null,
|
||||
inner_class = null,
|
||||
test_method = null
|
||||
}
|
||||
|
||||
|
||||
signal run_tests(what)
|
||||
|
||||
|
||||
func _ready():
|
||||
_ctrls.lbl_none.visible = true
|
||||
_ctrls.btn_script.visible = false
|
||||
_ctrls.btn_inner.visible = false
|
||||
_ctrls.btn_method.visible = false
|
||||
_ctrls.arrow_1.visible = false
|
||||
_ctrls.arrow_2.visible = false
|
||||
|
||||
# ----------------
|
||||
# Private
|
||||
# ----------------
|
||||
func _set_editor(which):
|
||||
_last_line = -1
|
||||
if(_cur_editor != null and _cur_editor.get_ref()):
|
||||
# _cur_editor.get_ref().disconnect('cursor_changed',Callable(self,'_on_cursor_changed'))
|
||||
_cur_editor.get_ref().caret_changed.disconnect(_on_cursor_changed)
|
||||
|
||||
if(which != null):
|
||||
_cur_editor = weakref(which)
|
||||
which.caret_changed.connect(_on_cursor_changed.bind(which))
|
||||
# which.connect('cursor_changed',Callable(self,'_on_cursor_changed'),[which])
|
||||
|
||||
_last_line = which.get_caret_line()
|
||||
_last_info = _editors.get_line_info()
|
||||
_update_buttons(_last_info)
|
||||
|
||||
|
||||
func _update_buttons(info):
|
||||
_ctrls.lbl_none.visible = _cur_script_path == null
|
||||
_ctrls.btn_script.visible = _cur_script_path != null
|
||||
|
||||
_ctrls.btn_inner.visible = info.inner_class != null
|
||||
_ctrls.arrow_1.visible = info.inner_class != null
|
||||
_ctrls.btn_inner.text = str(info.inner_class)
|
||||
_ctrls.btn_inner.tooltip_text = str("Run all tests in Inner-Test-Class ", info.inner_class)
|
||||
|
||||
_ctrls.btn_method.visible = info.test_method != null
|
||||
_ctrls.arrow_2.visible = info.test_method != null
|
||||
_ctrls.btn_method.text = str(info.test_method)
|
||||
_ctrls.btn_method.tooltip_text = str("Run test ", info.test_method)
|
||||
|
||||
# The button's new size won't take effect until the next frame.
|
||||
# This appears to be what was causing the button to not be clickable the
|
||||
# first time.
|
||||
call_deferred("_update_size")
|
||||
|
||||
func _update_size():
|
||||
custom_minimum_size.x = _ctrls.btn_method.size.x + _ctrls.btn_method.position.x
|
||||
|
||||
# ----------------
|
||||
# Events
|
||||
# ----------------
|
||||
func _on_cursor_changed(which):
|
||||
if(which.get_caret_line() != _last_line):
|
||||
_last_line = which.get_caret_line()
|
||||
_last_info = _editors.get_line_info()
|
||||
_update_buttons(_last_info)
|
||||
|
||||
|
||||
func _on_BtnRunScript_pressed():
|
||||
var info = _last_info.duplicate()
|
||||
info.script = _cur_script_path.get_file()
|
||||
info.inner_class = null
|
||||
info.test_method = null
|
||||
emit_signal("run_tests", info)
|
||||
|
||||
|
||||
func _on_BtnRunInnerClass_pressed():
|
||||
var info = _last_info.duplicate()
|
||||
info.script = _cur_script_path.get_file()
|
||||
info.test_method = null
|
||||
emit_signal("run_tests", info)
|
||||
|
||||
|
||||
func _on_BtnRunMethod_pressed():
|
||||
var info = _last_info.duplicate()
|
||||
info.script = _cur_script_path.get_file()
|
||||
emit_signal("run_tests", info)
|
||||
|
||||
|
||||
# ----------------
|
||||
# Public
|
||||
# ----------------
|
||||
func set_script_text_editors(value):
|
||||
_editors = value
|
||||
|
||||
|
||||
func activate_for_script(path):
|
||||
_ctrls.btn_script.visible = true
|
||||
_ctrls.btn_script.text = path.get_file()
|
||||
_ctrls.btn_script.tooltip_text = str("Run all tests in script ", path)
|
||||
_cur_script_path = path
|
||||
_editors.refresh()
|
||||
# We have to wait a beat for the visibility to change on
|
||||
# the editors, otherwise we always get the first one.
|
||||
await get_tree().process_frame
|
||||
_set_editor(_editors.get_current_text_edit())
|
||||
|
||||
|
||||
func get_script_button():
|
||||
return _ctrls.btn_script
|
||||
|
||||
|
||||
func get_inner_button():
|
||||
return _ctrls.btn_inner
|
||||
|
||||
|
||||
func get_test_button():
|
||||
return _ctrls.btn_method
|
||||
|
||||
|
||||
# not used, thought was configurable but it's just the script prefix
|
||||
func set_method_prefix(value):
|
||||
_editors.set_method_prefix(value)
|
||||
|
||||
|
||||
# not used, thought was configurable but it's just the script prefix
|
||||
func set_inner_class_prefix(value):
|
||||
_editors.set_inner_class_prefix(value)
|
||||
|
||||
|
||||
# Mashed this function in here b/c it has _editors. Probably should be
|
||||
# somewhere else (possibly in script_text_editor_controls).
|
||||
func search_current_editor_for_text(txt):
|
||||
var te = _editors.get_current_text_edit()
|
||||
var result = te.search(txt, 0, 0, 0)
|
||||
var to_return = -1
|
||||
|
||||
return to_return
|
||||
@@ -0,0 +1,65 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://0yunjxtaa8iw"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/RunAtCursor.gd" id="1"]
|
||||
[ext_resource type="Texture2D" uid="uid://cr6tvdv0ve6cv" path="res://addons/gut/gui/play.png" id="2"]
|
||||
[ext_resource type="Texture2D" uid="uid://6wra5rxmfsrl" path="res://addons/gut/gui/arrow.png" id="3"]
|
||||
|
||||
[node name="RunAtCursor" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_right = 1.0
|
||||
offset_bottom = -527.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="HBox" type="HBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="LblNoneSelected" type="Label" parent="HBox"]
|
||||
layout_mode = 2
|
||||
text = "<None>"
|
||||
|
||||
[node name="BtnRunScript" type="Button" parent="HBox"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "<script>"
|
||||
icon = ExtResource("2")
|
||||
|
||||
[node name="Arrow1" type="TextureButton" parent="HBox"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(24, 0)
|
||||
layout_mode = 2
|
||||
texture_normal = ExtResource("3")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="BtnRunInnerClass" type="Button" parent="HBox"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "<inner class>"
|
||||
icon = ExtResource("2")
|
||||
|
||||
[node name="Arrow2" type="TextureButton" parent="HBox"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(24, 0)
|
||||
layout_mode = 2
|
||||
texture_normal = ExtResource("3")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="BtnRunMethod" type="Button" parent="HBox"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "<method>"
|
||||
icon = ExtResource("2")
|
||||
|
||||
[connection signal="pressed" from="HBox/BtnRunScript" to="." method="_on_BtnRunScript_pressed"]
|
||||
[connection signal="pressed" from="HBox/BtnRunInnerClass" to="." method="_on_BtnRunInnerClass_pressed"]
|
||||
[connection signal="pressed" from="HBox/BtnRunMethod" to="." method="_on_BtnRunMethod_pressed"]
|
||||
255
samples/client/petstore/gdscript/addons/gut/gui/RunResults.gd
Normal file
255
samples/client/petstore/gdscript/addons/gut/gui/RunResults.gd
Normal file
@@ -0,0 +1,255 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
var _interface = null
|
||||
var _font = null
|
||||
var _font_size = null
|
||||
var _editors = null # script_text_editor_controls.gd
|
||||
var _output_control = null
|
||||
|
||||
@onready var _ctrls = {
|
||||
tree = $VBox/Output/Scroll/Tree,
|
||||
toolbar = {
|
||||
toolbar = $VBox/Toolbar,
|
||||
collapse = $VBox/Toolbar/Collapse,
|
||||
collapse_all = $VBox/Toolbar/CollapseAll,
|
||||
expand = $VBox/Toolbar/Expand,
|
||||
expand_all = $VBox/Toolbar/ExpandAll,
|
||||
hide_passing = $VBox/Toolbar/HidePassing,
|
||||
show_script = $VBox/Toolbar/ShowScript,
|
||||
scroll_output = $VBox/Toolbar/ScrollOutput
|
||||
}
|
||||
}
|
||||
|
||||
func _ready():
|
||||
var f = null
|
||||
if ($FontSampler.get_label_settings() == null) :
|
||||
f = get_theme_default_font()
|
||||
else :
|
||||
f = $FontSampler.get_label_settings().font
|
||||
var s_size = f.get_string_size("000 of 000 passed")
|
||||
_ctrls.tree.set_summary_min_width(s_size.x)
|
||||
|
||||
_set_toolbutton_icon(_ctrls.toolbar.collapse, 'CollapseTree', 'c')
|
||||
_set_toolbutton_icon(_ctrls.toolbar.collapse_all, 'CollapseTree', 'c')
|
||||
_set_toolbutton_icon(_ctrls.toolbar.expand, 'ExpandTree', 'e')
|
||||
_set_toolbutton_icon(_ctrls.toolbar.expand_all, 'ExpandTree', 'e')
|
||||
_set_toolbutton_icon(_ctrls.toolbar.show_script, 'Script', 'ss')
|
||||
_set_toolbutton_icon(_ctrls.toolbar.scroll_output, 'Font', 'so')
|
||||
|
||||
_ctrls.tree.hide_passing = true
|
||||
_ctrls.toolbar.hide_passing.button_pressed = false
|
||||
_ctrls.tree.show_orphans = true
|
||||
_ctrls.tree.item_selected.connect(_on_item_selected)
|
||||
|
||||
if(get_parent() == get_tree().root):
|
||||
_test_running_setup()
|
||||
|
||||
call_deferred('_update_min_width')
|
||||
|
||||
|
||||
func _test_running_setup():
|
||||
_ctrls.tree.hide_passing = true
|
||||
_ctrls.tree.show_orphans = true
|
||||
var _gut_config = load('res://addons/gut/gut_config.gd').new()
|
||||
_gut_config.load_panel_options('res://.gut_editor_config.json')
|
||||
set_font(
|
||||
_gut_config.options.panel_options.font_name,
|
||||
_gut_config.options.panel_options.font_size)
|
||||
|
||||
_ctrls.toolbar.hide_passing.text = '[hp]'
|
||||
_ctrls.tree.load_json_file('user://.gut_editor.json')
|
||||
|
||||
|
||||
func _set_toolbutton_icon(btn, icon_name, text):
|
||||
if(Engine.is_editor_hint()):
|
||||
btn.icon = get_theme_icon(icon_name, 'EditorIcons')
|
||||
else:
|
||||
btn.text = str('[', text, ']')
|
||||
|
||||
|
||||
func _update_min_width():
|
||||
custom_minimum_size.x = _ctrls.toolbar.toolbar.size.x
|
||||
|
||||
|
||||
func _open_script_in_editor(path, line_number):
|
||||
if(_interface == null):
|
||||
print('Too soon, wait a bit and try again.')
|
||||
return
|
||||
|
||||
var r = load(path)
|
||||
if(line_number != null and line_number != -1):
|
||||
_interface.edit_script(r, line_number)
|
||||
else:
|
||||
_interface.edit_script(r)
|
||||
|
||||
if(_ctrls.toolbar.show_script.pressed):
|
||||
_interface.set_main_screen_editor('Script')
|
||||
|
||||
|
||||
# starts at beginning of text edit and searches for each search term, moving
|
||||
# through the text as it goes; ensuring that, when done, it found the first
|
||||
# occurance of the last srting that happend after the first occurance of
|
||||
# each string before it. (Generic way of searching for a method name in an
|
||||
# inner class that may have be a duplicate of a method name in a different
|
||||
# inner class)
|
||||
func _get_line_number_for_seq_search(search_strings, te):
|
||||
if(te == null):
|
||||
print("No Text editor to get line number for")
|
||||
return 0;
|
||||
|
||||
var result = null
|
||||
var line = Vector2i(0, 0)
|
||||
var s_flags = 0
|
||||
|
||||
var i = 0
|
||||
var string_found = true
|
||||
while(i < search_strings.size() and string_found):
|
||||
result = te.search(search_strings[i], s_flags, line.y, line.x)
|
||||
if(result.x != -1):
|
||||
line = result
|
||||
else:
|
||||
string_found = false
|
||||
i += 1
|
||||
|
||||
return line.y
|
||||
|
||||
|
||||
func _goto_code(path, line, method_name='', inner_class =''):
|
||||
if(_interface == null):
|
||||
print('going to ', [path, line, method_name, inner_class])
|
||||
return
|
||||
|
||||
_open_script_in_editor(path, line)
|
||||
if(line == -1):
|
||||
var search_strings = []
|
||||
if(inner_class != ''):
|
||||
search_strings.append(inner_class)
|
||||
|
||||
if(method_name != ''):
|
||||
search_strings.append(method_name)
|
||||
|
||||
line = _get_line_number_for_seq_search(search_strings, _editors.get_current_text_edit())
|
||||
if(line != null and line != -1):
|
||||
_interface.get_script_editor().goto_line(line)
|
||||
|
||||
|
||||
func _goto_output(path, method_name, inner_class):
|
||||
if(_output_control == null):
|
||||
return
|
||||
|
||||
var search_strings = [path]
|
||||
|
||||
if(inner_class != ''):
|
||||
search_strings.append(inner_class)
|
||||
|
||||
if(method_name != ''):
|
||||
search_strings.append(method_name)
|
||||
|
||||
var line = _get_line_number_for_seq_search(search_strings, _output_control.get_rich_text_edit())
|
||||
if(line != null and line != -1):
|
||||
_output_control.scroll_to_line(line)
|
||||
|
||||
|
||||
|
||||
|
||||
# --------------
|
||||
# Events
|
||||
# --------------
|
||||
func _on_Collapse_pressed():
|
||||
collapse_selected()
|
||||
|
||||
|
||||
func _on_Expand_pressed():
|
||||
expand_selected()
|
||||
|
||||
|
||||
func _on_CollapseAll_pressed():
|
||||
collapse_all()
|
||||
|
||||
|
||||
func _on_ExpandAll_pressed():
|
||||
expand_all()
|
||||
|
||||
|
||||
func _on_Hide_Passing_pressed():
|
||||
_ctrls.tree.hide_passing = !_ctrls.toolbar.hide_passing.button_pressed
|
||||
_ctrls.tree.load_json_file('user://.gut_editor.json')
|
||||
|
||||
|
||||
func _on_item_selected(script_path, inner_class, test_name, line):
|
||||
if(_ctrls.toolbar.show_script.button_pressed):
|
||||
_goto_code(script_path, line, test_name, inner_class)
|
||||
if(_ctrls.toolbar.scroll_output.button_pressed):
|
||||
_goto_output(script_path, test_name, inner_class)
|
||||
|
||||
|
||||
|
||||
|
||||
# --------------
|
||||
# Public
|
||||
# --------------
|
||||
func add_centered_text(t):
|
||||
_ctrls.tree.add_centered_text(t)
|
||||
|
||||
|
||||
func clear_centered_text():
|
||||
_ctrls.tree.clear_centered_text()
|
||||
|
||||
|
||||
func clear():
|
||||
_ctrls.tree.clear()
|
||||
clear_centered_text()
|
||||
|
||||
|
||||
func set_interface(which):
|
||||
_interface = which
|
||||
|
||||
|
||||
func set_script_text_editors(value):
|
||||
_editors = value
|
||||
|
||||
|
||||
func collapse_all():
|
||||
_ctrls.tree.collapse_all()
|
||||
|
||||
|
||||
func expand_all():
|
||||
_ctrls.tree.expand_all()
|
||||
|
||||
|
||||
func collapse_selected():
|
||||
var item = _ctrls.tree.get_selected()
|
||||
if(item != null):
|
||||
_ctrls.tree.set_collapsed_on_all(item, true)
|
||||
|
||||
|
||||
func expand_selected():
|
||||
var item = _ctrls.tree.get_selected()
|
||||
if(item != null):
|
||||
_ctrls.tree.set_collapsed_on_all(item, false)
|
||||
|
||||
|
||||
func set_show_orphans(should):
|
||||
_ctrls.tree.show_orphans = should
|
||||
|
||||
|
||||
func set_font(font_name, size):
|
||||
pass
|
||||
# var dyn_font = FontFile.new()
|
||||
# var font_data = FontFile.new()
|
||||
# font_data.font_path = 'res://addons/gut/fonts/' + font_name + '-Regular.ttf'
|
||||
# font_data.antialiased = true
|
||||
# dyn_font.font_data = font_data
|
||||
#
|
||||
# _font = dyn_font
|
||||
# _font.size = size
|
||||
# _font_size = size
|
||||
|
||||
|
||||
func set_output_control(value):
|
||||
_output_control = value
|
||||
|
||||
|
||||
func load_json_results(j):
|
||||
_ctrls.tree.load_json_results(j)
|
||||
116
samples/client/petstore/gdscript/addons/gut/gui/RunResults.tscn
Normal file
116
samples/client/petstore/gdscript/addons/gut/gui/RunResults.tscn
Normal file
@@ -0,0 +1,116 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://4gyyn12um08h"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gut/gui/RunResults.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://dls5r5f6157nq" path="res://addons/gut/gui/ResultsTree.tscn" id="2_o808v"]
|
||||
|
||||
[sub_resource type="Image" id="Image_18d1g"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_8u17l"]
|
||||
image = SubResource("Image_18d1g")
|
||||
|
||||
[node name="RunResults" type="Control"]
|
||||
custom_minimum_size = Vector2(345, 0)
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 709.0
|
||||
offset_bottom = 321.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="Toolbar" type="HBoxContainer" parent="VBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
|
||||
[node name="Expand" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="Collapse" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="Sep" type="ColorRect" parent="VBox/Toolbar"]
|
||||
custom_minimum_size = Vector2(2, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LblAll" type="Label" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "All:"
|
||||
|
||||
[node name="ExpandAll" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="CollapseAll" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="Sep2" type="ColorRect" parent="VBox/Toolbar"]
|
||||
custom_minimum_size = Vector2(2, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HidePassing" type="CheckBox" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
text = "Passing"
|
||||
|
||||
[node name="Sep3" type="ColorRect" parent="VBox/Toolbar"]
|
||||
custom_minimum_size = Vector2(2, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LblSync" type="Label" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "Sync:"
|
||||
|
||||
[node name="ShowScript" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="ScrollOutput" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
icon = SubResource("ImageTexture_8u17l")
|
||||
|
||||
[node name="Output" type="Panel" parent="VBox"]
|
||||
self_modulate = Color(1, 1, 1, 0.541176)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Scroll" type="ScrollContainer" parent="VBox/Output"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Tree" parent="VBox/Output/Scroll" instance=ExtResource("2_o808v")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FontSampler" type="Label" parent="."]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 14.0
|
||||
text = "000 of 000 passed"
|
||||
|
||||
[connection signal="pressed" from="VBox/Toolbar/Expand" to="." method="_on_Expand_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/Collapse" to="." method="_on_Collapse_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/ExpandAll" to="." method="_on_ExpandAll_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/CollapseAll" to="." method="_on_CollapseAll_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/HidePassing" to="." method="_on_Hide_Passing_pressed"]
|
||||
@@ -0,0 +1,7 @@
|
||||
[gd_scene format=3 uid="uid://cvvvtsah38l0e"]
|
||||
|
||||
[node name="Settings" type="VBoxContainer"]
|
||||
offset_right = 388.0
|
||||
offset_bottom = 586.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
@@ -0,0 +1,144 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
|
||||
@onready var _ctrls = {
|
||||
shortcut_label = $Layout/lblShortcut,
|
||||
set_button = $Layout/SetButton,
|
||||
save_button = $Layout/SaveButton,
|
||||
cancel_button = $Layout/CancelButton,
|
||||
clear_button = $Layout/ClearButton
|
||||
}
|
||||
|
||||
signal changed
|
||||
signal start_edit
|
||||
signal end_edit
|
||||
|
||||
const NO_SHORTCUT = '<None>'
|
||||
|
||||
var _source_event = InputEventKey.new()
|
||||
var _pre_edit_event = null
|
||||
var _key_disp = NO_SHORTCUT
|
||||
|
||||
var _modifier_keys = [KEY_ALT, KEY_CTRL, KEY_META, KEY_SHIFT]
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
set_process_unhandled_key_input(false)
|
||||
|
||||
|
||||
func _display_shortcut():
|
||||
if(_key_disp == ''):
|
||||
_key_disp = NO_SHORTCUT
|
||||
_ctrls.shortcut_label.text = _key_disp
|
||||
|
||||
|
||||
func _is_shift_only_modifier():
|
||||
return _source_event.shift_pressed and \
|
||||
!(_source_event.alt_pressed or \
|
||||
_source_event.ctrl_pressed or \
|
||||
_source_event.meta_pressed) \
|
||||
and !_is_modifier(_source_event.keycode)
|
||||
|
||||
|
||||
func _has_modifier(event):
|
||||
return event.alt_pressed or event.ctrl_pressed or \
|
||||
event.meta_pressed or event.shift_pressed
|
||||
|
||||
|
||||
func _is_modifier(keycode):
|
||||
return _modifier_keys.has(keycode)
|
||||
|
||||
|
||||
func _edit_mode(should):
|
||||
set_process_unhandled_key_input(should)
|
||||
_ctrls.set_button.visible = !should
|
||||
_ctrls.save_button.visible = should
|
||||
_ctrls.save_button.disabled = should
|
||||
_ctrls.cancel_button.visible = should
|
||||
_ctrls.clear_button.visible = !should
|
||||
|
||||
if(should and to_s() == ''):
|
||||
_ctrls.shortcut_label.text = 'press buttons'
|
||||
else:
|
||||
_ctrls.shortcut_label.text = to_s()
|
||||
|
||||
if(should):
|
||||
emit_signal("start_edit")
|
||||
else:
|
||||
emit_signal("end_edit")
|
||||
|
||||
# ---------------
|
||||
# Events
|
||||
# ---------------
|
||||
func _unhandled_key_input(event):
|
||||
if(event is InputEventKey):
|
||||
if(event.pressed):
|
||||
if(_has_modifier(event) and !_is_modifier(event.get_keycode_with_modifiers())):
|
||||
_source_event = event
|
||||
_key_disp = OS.get_keycode_string(event.get_keycode_with_modifiers())
|
||||
else:
|
||||
_source_event = InputEventKey.new()
|
||||
_key_disp = NO_SHORTCUT
|
||||
_display_shortcut()
|
||||
_ctrls.save_button.disabled = !is_valid()
|
||||
|
||||
|
||||
func _on_SetButton_pressed():
|
||||
_pre_edit_event = _source_event.duplicate(true)
|
||||
_edit_mode(true)
|
||||
|
||||
|
||||
func _on_SaveButton_pressed():
|
||||
_edit_mode(false)
|
||||
_pre_edit_event = null
|
||||
emit_signal('changed')
|
||||
|
||||
|
||||
func _on_CancelButton_pressed():
|
||||
_edit_mode(false)
|
||||
_source_event = _pre_edit_event
|
||||
_key_disp = to_s()
|
||||
_display_shortcut()
|
||||
|
||||
|
||||
func _on_ClearButton_pressed():
|
||||
clear_shortcut()
|
||||
|
||||
# ---------------
|
||||
# Public
|
||||
# ---------------
|
||||
func to_s():
|
||||
return OS.get_keycode_string(_source_event.get_keycode_with_modifiers())
|
||||
|
||||
|
||||
func is_valid():
|
||||
return _has_modifier(_source_event) and !_is_shift_only_modifier()
|
||||
|
||||
|
||||
func get_shortcut():
|
||||
var to_return = Shortcut.new()
|
||||
to_return.events.append(_source_event)
|
||||
return to_return
|
||||
|
||||
|
||||
func set_shortcut(sc):
|
||||
if(sc == null or sc.events == null || sc.events.size() <= 0):
|
||||
clear_shortcut()
|
||||
else:
|
||||
_source_event = sc.events[0]
|
||||
_key_disp = to_s()
|
||||
_display_shortcut()
|
||||
|
||||
|
||||
func clear_shortcut():
|
||||
_source_event = InputEventKey.new()
|
||||
_key_disp = NO_SHORTCUT
|
||||
_display_shortcut()
|
||||
|
||||
|
||||
func disable_set(should):
|
||||
_ctrls.set_button.disabled = should
|
||||
|
||||
func disable_clear(should):
|
||||
_ctrls.clear_button.disabled = should
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user