Git Product home page Git Product logo

stencilswiftkit's Introduction

StencilSwiftKit

CircleCI CocoaPods Compatible Platform Swift 5.0

StencilSwiftKit is a framework bringing additional Stencil nodes & filters dedicated to Swift code generation.

Tags

  • Macro & Call
    • {% macro <Name> <Params> %}…{% endmacro %}
    • Defines a macro that will be replaced by the nodes inside of this block later when called
    • {% call <Name> <Args> %}
    • Calls a previously defined macro, passing it some arguments
  • Set
    • {% set <Name> %}…{% endset %}
    • Renders the nodes inside this block immediately, and stores the result in the <Name> variable of the current context.
  • Import
    • {% import "common.stencil" %}
    • Imports any macro & set definitions from common.stencil into the current context.
  • Map
    • {% map <Variable> into <Name> using <ItemName> %}…{% endmap %}
    • Apply a map operator to an array, and store the result into a new array variable <Name> in the current context.
    • Inside the map loop, a maploop special variable is available (akin to the forloop variable in for nodes). It exposes maploop.counter, maploop.first, maploop.last and maploop.item.

Filters

  • String filters:
    • basename: Get the filename from a path.
    • camelToSnakeCase: Transforms text from camelCase to snake_case. By default it converts to lower case, unless a single optional argument is set to "false", "no" or "0".
    • contains: Check if a string contains a specific substring.
    • dirname: Get the path to the parent folder from a path.
    • escapeReservedKeywords: Escape keywords reserved in the Swift language, by wrapping them inside backticks so that the can be used as regular escape keywords in Swift code.
    • hasPrefix / hasSuffix: Check if a string starts/ends with a specific substring.
    • lowerFirstLetter: Lowercases only the first letter of a string.
    • lowerFirstWord: Lowercases only the first word of a string.
    • replace: Replaces instances of a substring with a new string.
    • snakeToCamelCase: Transforms text from snake_case to camelCase. By default it keeps leading underscores, unless a single optional argument is set to "true", "yes" or "1".
    • swiftIdentifier: Transforms an arbitrary string into a valid Swift identifier (using only valid characters for a Swift identifier as defined in the Swift language reference). In "pretty" mode, it will also apply the snakeToCamelCase filter afterwards, and other manipulations if needed for a "prettier" but still valid identifier.
    • upperFirstLetter: Uppercases only the first character
  • Number filters:
    • int255toFloat
    • hexToInt
    • percent

Stencil.Extension & swiftStencilEnvironment

This framework also contains helper methods for Stencil.Extension and Stencil.Environment, to easily register all the tags and filters listed above on an existing Stencil.Extension, as well as to easily get a Stencil.Environment preconfigured with both those tags & filters Extension.

Parameters

This framework contains an additional parser, meant to parse a list of parameters from the CLI. For example, using Commander, if you receive a [String] from a VariadicOption<String>, you can use the parser to convert it into a structured dictionary. For example:

["foo=1", "bar=2", "baz.qux=hello", "baz.items=a", "baz.items=b", "something"]

will become

[
  "foo": "1",
  "bar": "2",
  "baz": [
    "qux": "hello",
    "items": [
      "a",
      "b"
    ]
  ],
  something: true
]

For easier use, you can use the StencilContext.enrich(context:parameters:environment:) function to add the following variables to a context:

  • param: the parsed parameters using the parser mentioned above.
  • env: a dictionary with all available environment variables (such as PATH).

Licence

This code and tool is under the MIT Licence. See the LICENCE file in this repository.

stencilswiftkit's People

Contributors

alisoftware avatar antondomashnev avatar bejar37 avatar diogot avatar djbe avatar fortmarek avatar ilyapuchka avatar jpsim avatar krzysztofzablocki avatar liquidsoul avatar rahul0x24 avatar redryerye avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stencilswiftkit's Issues

question: how to combine keys of multiple dictionaries in a single list and remove duplicates

hello swiftgen team!
i am working with stencil in the context of a custom swiftgen template.
i am attempting to iterate over a list of dictionaries and extract all the unique keys which will then represent the cases of an enum.
something like this:

{% macro extractUniqueKeys dictionaries %}
public enum AnalyticsPropertyKey: String {
  {% for dict in dictionaries %}
  {% for key,value in dict %}
  // Only want to execute setting key name and declaring case if I haven't already found that key!
  {% set keyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
    case {{keyName}} = "{{key}}"
  {% endfor %}
  {% endfor %}
}
{% endmacro %}

Is there a way to do this? I also asked on the stencil repo, in case that's the better forum. thank you!

Release 2.6.0

I'm not sure if I consider this a breaking change:

Migrated to PathKit for url filters. The dirname will return '.' for a filename without base directory.

I think it's more of a bug fix TBH (we're bringing the output in line with PathKit).

Depending on our choice, the version'll be 2.6.0 or 3.0.0.

HEX Data for Colors.xcassets

Trying to add comments to my custom template for colors from .xcassets Catalog. But seems there is no that data.
Is it possible to get data from Contents.json from each color? Actually, there is contains a data for that...

Currently we have the next data in {asset} variable in templates:

["type": "color", "value": "backgroundGreen", "name": "backgroundGreen"]

Actually the result should be like this:

/// x339933
static let backgroundGreen = UIColor(name: "backgroundGreen")

This will really be helpful for development since you can't remember all values for each name. When you checking UI design and colors - that comment will help to quickly find the right value without "xcassets" Catalog checking.
Thanks!

For range loop or alternative

I've done my due diligence but I can't seem to find a way to do this:

{% for i in range(1,5) %}
... do something with i ...
{% endfor %}

I apologize beforehand if there's an obvious way to do it but I can't seem to find it.
The closest thing I've been to a solution is using an array that has at least the number of elements I want, doing a map to get the counter and then filter the counters outside the range (which is way too ugly)

I also tried to create an array variable using {% set %} to no use.
And I also tried "1,2,3,4"|split but there's no such filter.

Am I missing something obvious?

defect on getters / setters - iterating over nodes in context

I have confirmed this sample working fine.

stencil string 
{% for article in operations %}
    <li>{{ article.title }} by {{ article.author }}</li>
{% endfor %} 

struct Article {
        let name: String
        let author: String
    }

let articleContext = [
        "articles": [
            Article(name: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
            Article(name: "Memory Management with ARC", author: "Kyle Fuller"),
        ]
    ]

 let environment = stencilSwiftEnvironment()
 let template = StencilSwiftTemplate(templateString:stencilString,environment:environment)
 let generated = try template.render(["articles": articleContext["articles"]])

For my case, I have some swift protobuffer classes that are auto generated by Apple.

here's the actual class
https://github.com/johndpope/swift-grpc-tensorflow/blob/master/Sources/op_def.pb.swift

Basically it implements SwiftProtobuf.Message
public struct Tensorflow_OpDef: SwiftProtobuf.Message (protocol only)

from what I can see - the only difference between the
struct Article
and
struct Tensorflow_OpDef

is the getters / setters

public var name: String {
  get {return _storage._name}
  set {_uniqueStorage()._name = newValue}
}

when I reference the internal code / then it works.

{% for op in operations %}
name:{{ op.**_storage._name** }}
{% endfor %}

Expected

{% for op in operations %}
name:{{ op.name }}
{% endfor %}

doesn't render any results.

(I get that this may not be fixable. but maybe some warnings could be added to help debug.)

screen shot 2017-05-31 at 3 21 26 pm

Create enum from multiple JSONs removing duplicated

I have this simple custom template:

// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen

{% if files %}
{% macro fileBlock file %}
  {% call documentBlock file.document %}
{% endmacro %}
{% macro documentBlock document %}
  {% for key,value in document.metadata.properties %}
  {% if key == "tests" %}
  {% for testKey,testValue in value.properties %}
    {%- set propertyName %}{{testKey|lowerFirstWord}}{% endset -%}
    case {{propertyName}} = "{{testKey}}"
  {% endfor %}
  {% endif %}
  {% endfor %}
{% endmacro %}
import Foundation

// MARK: - ABTest Names

enum ABTestNames: String {
{% for file in files %}
    {% call fileBlock file %}
{% endfor %}

}
{% endif %}

It reads from multiple JSON files and creates an enum with the keys of the elements in the "tests" property.
The problem is that some of these keys are the same in different files and I want to remove the duplicated elements.

Is there a way to do that?

reusable macro templates

I've a lot of similar templates with a lot of common macros, I'd like to just have a copy of each single macro stored inside a common file included by all templates. Unfortunately it looks like I can't just {% include .. %} a macro template and use the macros defined there. When I do call them they are not defined. Is it possible to export macros in a way that could be used from the "outer" context ? thanks in advance

Map only works with strings

I am trying to filter an array using map. In this case I filter Sourcery variables:

{% map type.allVariables into vars using variable %}{% if variable.readAccess != "private" and variable.readAccess != "fileprivate" and not variable.isComputed %}{{ variable }}{% endif %}{% endmap %}

Afterwards I want to print these filtered vars.
{% for variable in vars where variable != "" %}{{ variable.name }}: {{ variable.name }}{% if not forloop.last %}, {% endif %}{% endfor %}

A get following Exception:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<TtGCs19_SwiftStringStorageVs6UInt16 0x7fb2a2df64f0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'

My guess is the problem lies here:

let mappedValues: [String] = try values.enumerated().map { index, item in

The MapNode expects the mapped 0utput to be a String.
I could dig into this and provide a fix if you recognize it as an issue.

Add new increment filter to Numbers

I'm using Sourcery, I can't create my own filters and I've tried many times to increment an Integer, I'd like to ask if it's possible to add this as a new filter for numbers here at StencilSwiftKit (or please help me how I'd achieve this without a new filter)

For context what I'd like to do in Stencil:
{{ forloop.counter + 5 }}

Unfortunately this is not yet supported (see for reference: stencilproject/Stencil#117)

I'd like to propose a new filter that would look similar to this:
{{ forloop.counter|increment:5 }}

Any thoughts? Again if I can do that with the existing filter please let me know

Calling macro with a parameter invoking a filter crashes

So I have a macro
{% macro someMacro someParameter %}

And when I attempt to invoke it like this:
{% call someMacro myType.storedVariables|annotated:"someAnnotation" %}

I get this fatal exception:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<SourceryRuntime.Struct 0x7f94a2e1a5a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key storedVariables|annotated:"json".'

Calling the macro without the filter on storedVariables works just fine:
{% call someMacro myType.storedVariables %}

swiftIdentifier converts all caps to title case

I'm currently using this through SwiftGen for generating localized strings.

If I have the strings PDF-title it returns the identifier PDFTitle, but if it's passed PDF it results in the string Pdf which isn't the correct capitalization for an abbreviation. It seems like there is a difference in behaviour if the passed string has some capitals vs all capitals.

Pool swift reserved words en masse into a single list.

it would be useful to have a list of swift reserved words that that could be separately maintained.

I came across this just today
https://github.com/apple/swift-protobuf/tree/master/Reference

kind of fits the bill.
https://github.com/apple/swift-protobuf/blob/master/Reference/generated_swift_names_fields.pb.swift

Having trouble using the java / c++ code generation tool with antlr.
being able to plug in a list would pay dividends down the track.
related.
antlr/antlr4#1851

Map Node is not working as expected

I have been trying to use the map function but without success.
I used this documentation sample https://github.com/SwiftGen/StencilSwiftKit/blob/master/Documentation/tag-map.md

// map the list without item name
{% map list into result1 %}{{maploop.item|uppercase}}{% endmap %} 

// map with item name
{% map list into result2 using item %}{{item}}{{item|uppercase}}{% endmap %} 

// map using the counter variable
{% map list into result3 using item %}{{maploop.counter}} - {{item}}{% endmap %} 

This is what it is rendering, always empty.

// map the list without item name

// map with item name

// map using the counter variable

SwiftIdentifier filter: add modes for more flexibility

The current modes will always uppercase the first letter of each component, which is detrimental for some users that prefer to have the same keys as (for example) in their strings files.

Our current modes are:

  • normal: converts to a valid identifier, uppercases first letter of each component
  • pretty: same as normal + snakeToCamelCase

I propose adding some sort of raw mode, that only converts to a valid identifier, leaving the rest of the string as is. If need be, we can consider renaming the current modes to new names that better reflect their functionality.

For backwards compatibility, if we rename modes, we can always keep the old names as aliases for a few versions.

Replace filter: unescape backslashes

In a template, if I try:
value|replace:"\n","\\n"

The search and replace strings become "\\n" and "\\\\n" respectively. I think we should un-escape these backslashes, so that users correctly can search for newlines (for example). Or is this something that we should handle in Stencil itself?

Mutations inside forloop not visible outside

When mutating a variable inside a for-loop, these changes get reverted on loop exit.
This is quite unlike most programming languages. Unfortunately this makes you use more boilerplate code inside templates than necessary.

generating swift method signatures / extra comma

while I have been able to get some code smashed out using stencil
https://github.com/johndpope/tensorflow/blob/swift/tensorflow/swift/misc/OperationDefinitions.stencil

https://github.com/johndpope/tensorflow/blob/swift/tensorflow/swift/misc/Operations.swift

I'm using this code

func {{ op._storage._name}}(scope:Scope, {% for arg in op._storage._inputArg %} {{arg.name}}:tf.Output,{{arg.type}} {% endfor %}) -> ({% for arg in op._storage._outputArg %} {{arg.name}}:tf.Output, {% endfor %}) {

but end up with this extra , at end
func AccumulatorApplyGradient(scope:Scope, handle:tf.Output, local_step:tf.Output, gradient:tf.Output**,** ) -> () {

I noticed this c++ template which is used to generate swift classes in antlr has this syntax
https://github.com/janyou/ANTLR-Swift-Target/blob/master/org/antlr/v4/tool/templates/codegen/Swift/Swift.stg

eg.
public static let <lexer.tokens:{k | =<lexer.tokens.(k)>}; separator=", ", wrap, anchor>

it might be worth cherry picking some of the logic here.

Slightly unrelated / (and not an issue) but you maybe interested
This is every language gramma file from antlr spat out as swift tokens / parsers
https://github.com/johndpope/ANTLR-Swift-Target/tree/master/gen/grammars-v4

These base tokens / parsers are generated via c++ template(stg) in antlr then java.
https://github.com/johndpope/Antlr-Swift-runtime

I had envisaged one day that these could be generated from swift.
perhaps with this library

Allow to use strings filters on CustomStringConvertable

This was proposed in #58. The idea is that at the moment all strings filters from Filters+Strings... expect String parameter and throw exception if it's not satisfied, however we could change this rule to accept all parameters that can be described as a string, which CustomStringConvertable is made for 😄

We'll also make use of it in Sourcery

Change our filters to accept a `mode` parameter instead of a boolean.

Some of our current filters accept a boolean parameter, which isn't always clear "what" that parameter is for:

  • camelToSnakeCase: lowercase the components or not
  • snakeToCamelCase: trim empty components

For some of our other filters, we already use a "mode" string parameter, that says a bit more about what it does:

  • removeNewlines: all or leading
  • swiftIdentifier: normal or pretty

Suggestions for modes:

  • camelToSnakeCase: lowercase or none
  • snakeToCamelCase: trim-empty or none

How can I keep {{ }} in stencil?

want a output with {{ }} like {{(key)}}, how can I keep {{ }} ?
Example:

args.forEach { key, value in
result = result.replacingOccurrences(of: "{{(key)}}", with: value)
}

but output comes as:
args.forEach { key, value in
result = result.replacingOccurrences(of: "", with: value)
}

`set` tag not respecting scope in `for` loop

I'm trying to create a variable that is updated within the scope of a for tag, but have been unsuccessful:
In the following example, lastCaseName is never mutated, always taking the value initial.
Is this possible with Stencil or perhaps swifttemplate?

{% for root in types.implementing.BeRoot %}
    {% set lastCaseName %}initial{% endset %}
    class func reduce(_ state: State, with event: Event) -> State {
        switch(state.mode, event) {
        {% for case in root.cases %}
            case (.{{ lastCaseName }}, .did{{ lastCaseName|upperFirstLetter  }}):
            return state.with { $0.mode = {{ case.name }} }
            {% set lastCaseName %}{{ case.name }}{% endset %}
        {% endfor %}
        return state        
    }
}
{% endfor %}

Build failure using Swift 4.2 from Xcode 10 Beta

I'm running into an issue building StencilSwiftKit with SPM using Swift 4.2.

/.build/checkouts/StencilSwiftKit.git-5443537928707802040/Sources/SwiftIdentifier.swift:16:55: error: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
private let headRanges: [CountableClosedRange<Int>] = [

It looks like the same issue was detected by the Swift source compatibility suite: https://bugs.swift.org/browse/SR-8046

Has anyone else seen this / know of a work around?

Test Swift 4.2 (or drop support for it)

As discussed in #127, we need to decide what to do with Swift 4.2.

Do we expect lots of people to still use Swift 4.2? Any platform that supports Swift 4.2 also supports Swift 5, so we may as well drop it?

Make `Filter`s public

Is there a particular reason the filters are scoped internal? It would be nice if they were public to use the filters directly in other customs filters.

Add a removeNewLinesAndSpaces filter

So we can write complex if conditions like the {% macro className %} in multiple lines to make it more readable and have it compact the output. Until Stencil supports ejs-line newline-stripping tags

Migrate some filters to use parameters

Some Filters were created at a time when Stencil didn't support filter parameters yet.

As a result, we have some filters that have been duplicated to allow some variations, but now that Stencil filters support parameters, these duplications can be removed and changed to a filter parameter instead.

One typical example is filters StringFilters.snakeToCamelCase + StringFilters.snakeToCamelCaseNoPrefix that can be unified to a single StringFilters.snakeToCamelCase filter with a parameter (prefix: Bool)

new release

Hello 👋

Currently StencilSwiftKit stable branch depends on Stencil 0.14.0, which depends on PathKit 1.0.0. But last tagged version (StencilSwiftKit 2.7.2) depends on Stencil 0.13.1, which depends on PathKit 0.9.0. The change of major version in PathKit makes it impossible to use a fixed StencilSwiftKit version, if you also depend on other packages that depend on PathKit 1.0.0.

Would it be possible to have a new release of StencilSwiftKit so it is possible to depend on it and on PathKit 1.0.0 at the same time without having to use StencilSwiftKit stable branch?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.