Git Product home page Git Product logo

gradle-fork-plugin's Introduction

Neva logo

Apache License, Version 2.0, January 2004 GitHub stars

Gradle Fork Plugin

Description

Project generator based on live archetypes (example projects).

Props Dialog

Interactive gradle.properties file generator (user-friendly / by GUI dialog).

Props Dialog

Both screenshots come from Gradle AEM Multi (example usage). Related project specific configuration:


Newcomers of Gradle Build System very often complain about that in Gradle there is no Maven's archetype like mechanism OOTB. This plugin tries to fill that gap.

Assumptions

  • Instead of creating a virtual project aka Maven Archetype with placeholders, plugin allows to treat any existing project like a base for a new project.
  • It is easier to copy rich example project and remove redundant things than creating project from archetype and looking for missing things.
  • From business perspective, plugin allows to automate rebranding at code level (perform massive renaming, repackaging).
  • Maintenance of real / working example projects is probably easier than maintaining archetypes (there is no need to regenerate project every time to prove that archetype is working properly).

Plugin is also useful for generating gradle.user.properties file in a **user-friendly way.

You liked or used plugin? Don't forget to star this project on GitHub :)

Table of Contents

Usage

Sample build script

buildscript {
  repositories {
      jcenter()
      maven { url  "https://dl.bintray.com/neva-dev/maven-public" }
  }
  dependencies {
      classpath 'com.neva.gradle:fork-plugin:3.0.5'
  }
}

apply plugin: 'com.neva.fork'

fork {
    config /* 'default', */ { // project forking configuration
        // settings:
        
//      textFiles = [
//          "**/*.gradle", "**/*.xml", "**/*.properties", "**/*.js", "**/*.json", "**/*.css", "**/*.scss",
//          "**/*.java", "**/*.kt", "**/*.kts", "**/*.groovy", "**/*.html", "**/*.jsp"
//      ]
        
//      executableFiles = [
//          "**/*.sh",
//          "**/*.bat",
//          "**/gradlew",
//          "**/mvnw"
//      ]

        // rules:
        cloneFiles()
        moveFiles([
                "/com/company/app/example": "/{{projectGroup|substitute('.', '/')}}/{{projectName}}",
                "/example": "/{{projectName}}"
        ])
        replaceContents([
                "com.company.app.example": "{{projectGroup}}.{{projectName}}",
                'com.company.app': "{{projectGroup}}",
                "Example": "{{projectLabel}}",
                "example": "{{projectName}}",
        ])
    }
    config 'copy', { // additional configuration, for demo purpose
        cloneFiles()
    }
    /*
    inPlaceConfig 'properties', { // predefined configuration for interactively generating 'gradle.user.properties' file
        copyTemplateFile("gradle/fork/gradle.user.properties.peb")
    }
    */
}

Defining and executing configurations

Fork plugin allows to have multiple fork configurations defined. In above sample build script, there are 3 configurations defined:

  1. Configuration fork with the purpose of creating a new project based on existing one. In detail, it will:

    • Prompt to fill or update all variables detected in rules like moveFiles, replaceContents and occurrences of variables in text files.
    • Copy all project files respecting filtering defined in .gitignore files.
    • Rename directories using rules with properties injecting.
    • Replace contents using rules with properties injecting.

    Executable by command line:

    gradlew fork
  2. Predefined configuration named props with the purpose of creating initial configuration before building project (generating gradle.user.properties file). In detail, it will:

    • Prompt to fill or update all variables detected in template file located at path gradle/fork/gradle.user.properties.peb.
    • Combine prompted variable values with template file to finally save a file containing user specific properties (like repository credentials etc).

    Executable by command line:

    gradlew props
  3. Additional configuration named copy just for demonstrating purpose (which is only copying files / not updating them)

    gradlew copy

Each configuration defines their own task with same name. So that it is possible to execute more than one configuration, e.g:

gradlew copyStuff renameOther

Providing properties

Properties can be provided by (order makes precedence):

  1. File which path could be specified as command line parameter:

    gradlew fork -Pfork.properties=fork.properties

    Such file should be in format:

    targetPath=../sample
    projectGroup=com.neva.app
    projectName=sample
    projectLabel=Sample
  2. Each property defined separately as command line parameter:

    gradlew fork -PforkProp.projectName=sample -PforkProp.projectLabel=Sample -PforkProp.targetPath=../sample -PforkProp.package=com.neva.app.sample
  3. GUI / properties dialog

    Props Dialog

    This dialog is always displayed to allow amending values provided by command line or properties file.

    To disable it, use command line parameter:

    gradlew fork -Pfork.interactive=false
  4. Mixed approach.

Defining project properties

Configuring of project properties can be enhanced by providing properties definitions which can be used for property value validation, e.g.:

fork {
    properties {
        define("enableSomething") { checkbox(defaultValue = true) }
        define("someUserName") { text(defaultValue = System.getProperty("user.name")) }
        define("projectGroup") { text(defaultValue = "org.neva") }
        define("someJvmOpts") {
            optional()
            text(defaultValue = "-server -Xmx1024m -XX:MaxPermSize=256M -Djava.awt.headless=true")
            validator { if (!property.value.startsWith("-")) error("This is not a JVM option!") }
        }
    }
}

Property definition

Property definition can consists of:

  • type specification: type = TYPE_NAME
    • there are six types available: TEXT (default one), CHECKBOX (representing boolean), PASSWORD (always encrypted), SELECT (list of options), PATH & URL.
    • there is default convention of type inference using property name (case insensitive):
      • ends with "password" -> PASSWORD
      • starts with "enable", "disable" -> CHECKBOX
      • ends with "enabled", "disabled" -> CHECKBOX
      • ends with "url" -> URL
      • ends with "path" -> PATH
      • else -> TEXT
  • default value specification: defaultValue = System.getProperty("user.name")
    • if no value would be provided for property defaultValue is used
  • declaring property as optional: optional()
    • by default all properties are required
  • specifying custom validator: validator = {if (!value.startsWith("-")) error("This is not a JVM option!")}
    • by default URL & PATH properties gets basic validation which can be overridden or suppressed: validator = {}

Password encryption

Passwords kept as plaintext in gradle.user.properties file can be problematic especially when you have to input there your private password ;-).

That's why Gradle Fork Plugin by default encrypts all PASSWORD properties (those which name ends with "password" or marked explicitly as password in their definition using password()). This way generated gradle.user.properties file wont ever again contain any password plaintext.

Passwords are automatically dencrypted and available via standard Gradle method project.findProperty() when plugin com.neva.fork.props is applied.

import com.neva.gradle.fork.PropsExtension

allprojects {
    plugins.apply("com.neva.fork.props")

    repositories {
        jcenter()
        maven {
            url = uri("https://nexus.company.com/content/groups/private")
            credentials {
                username = the<PropsExtension>().get("nexus.user")
                password = the<PropsExtension>().get("nexus.password")
            }
        }
    }
}

Advanced options

  • fork.verbose=true - fail build when GUI dialog is closed (Execute button not clicked),
  • fork.cached=false - skip filling dialog with values previously filled.

Sample output

After executing command gradlew fork, there will be a cloned project with correctly changed directory names, with replaced project name and label in text files (all stuff being previously performed manually).

Cloning files from C:\Users\krystian.panek\Projects\example to ..\sample
Copying file from C:\Users\krystian.panek\Projects\example\.editorconfig to ..\sample\.editorconfig
...
Moving file from C:\Users\krystian.panek\Projects\example\apps\example\content.xml to ..\sample\apps\sample\content.xml
...
Replacing 'Example' with 'Sample' in file C:\Users\krystian.panek\Projects\sample\app\build.gradle
Replacing 'com.company.aem.example' with 'com.neva.aem.sample' in file C:\Users\krystian.panek\Projects\sample\app\common\build.gradle
Replacing 'example' with 'sample' in file C:\Users\krystian.panek\Projects\sample\app\common\src\main\content\META-INF\vault\filter.xml

Then such forked project could be saved in VCS and each developer after cloning it could perform a setup very easily using command gradlew props to provide credentials to e.g Maven repositories, deployment servers etc before running application build that requires such data to be specified in gradle.user.properties file.

Copying file from C:\Users\krystian.panek\Projects\sample\gradle\fork\gradle.user.properties to C:\Users\krystian.panek\Projects\sample\gradle.user.properties
Expanding properties in file C:\Users\krystian.panek\Projects\sample\gradle.user.properties

License

Gradle Fork Plugin is licensed under the Apache License, Version 2.0 (the "License")

gradle-fork-plugin's People

Contributors

github-actions[bot] avatar jean-khechfe-wttech avatar krystian-panek-wttech avatar mierzwid avatar pun-ky avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

mierzwid

gradle-fork-plugin's Issues

File in use exception after filling properties

> Task :props FAILED
Build failure
Execution failed for task ':props'.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':props'.
> java.nio.file.FileSystemException: C:\Users\krystian.panek\Projects\gradle-aem-multi\gradle.user.properties: Proces nie mo▒e uzyska▒ dost▒pu do pliku, poniewa▒ jest on u▒ywany przez inny proces.

why? when project is open in intellij, it could prevent file saving of 'gradle.user.properties' occassionally

to fix it:

a) close IntelliJ, rerun gradlew props
b) run gradlew --stop then rerun gradlew props

this affects only Windows machines

any ideas how to fix / avoid it?

NPE in file chooser

Caused by: com.neva.gradle.fork.ForkException: Fork properties GUI dialog cannot be opened!
Please run 'sh gradlew --stop' then try again.
Ultimately run command with '--no-daemon' option.
        at com.neva.gradle.fork.gui.PropertyDialog$Companion.make(PropertyDialog.kt:169)
        at com.neva.gradle.fork.config.Config.promptFillGui(Config.kt:166)
        at com.neva.gradle.fork.config.Config.promptFill(Config.kt:122)
        at com.neva.gradle.fork.config.Config.access$promptFill(Config.kt:21)
        at com.neva.gradle.fork.config.Config$promptedProperties$2.invoke(Config.kt:29)
        at com.neva.gradle.fork.config.Config$promptedProperties$2.invoke(Config.kt:21)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at com.neva.gradle.fork.config.Config.getPromptedProperties(Config.kt)
        at com.neva.gradle.fork.config.Config.validate(Config.kt:293)
        at com.neva.gradle.fork.config.Config.evaluate(Config.kt:287)
        at com.neva.gradle.fork.tasks.ConfigTask.evaluate(ConfigTask.kt:18)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:49)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:42)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:28)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:721)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:688)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$3.run(ExecuteActionsTaskExecuter.java:539)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:524)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:507)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$300(ExecuteActionsTaskExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:258)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:247)
        at org.gradle.internal.execution.steps.ExecuteStep.lambda$execute$1(ExecuteStep.java:33)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:33)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:26)
        at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:63)
        at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:35)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:49)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:34)
        at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:43)
        at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:73)
        at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:54)
        at org.gradle.internal.execution.steps.CatchExceptionStep.execute(CatchExceptionStep.java:34)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:44)
        at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:54)
        at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:38)
        at org.gradle.internal.execution.steps.CacheStep.executeWithoutCache(CacheStep.java:153)
        at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:67)
        at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:41)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:49)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:44)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:33)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:38)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:24)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:92)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:85)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:39)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:76)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:37)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:36)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:26)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:94)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:49)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:79)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:53)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:74)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:78)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:78)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:39)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:40)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:28)
        at org.gradle.internal.execution.impl.DefaultWorkExecutor.execute(DefaultWorkExecutor.java:33)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:174)
        ... 30 more
Caused by: java.lang.NullPointerException
        at com.sun.java.swing.plaf.windows.WindowsFileChooserUI.updateUseShellFolder(WindowsFileChooserUI.java:498)
        at com.sun.java.swing.plaf.windows.WindowsFileChooserUI.installComponents(WindowsFileChooserUI.java:212)
        at com.sun.java.swing.plaf.windows.WindowsFileChooserUI.installUI(WindowsFileChooserUI.java:149)
        at com.neva.gradle.fork.gui.PropertyDialog.<init>(PropertyDialog.kt:112)
        at com.neva.gradle.fork.gui.PropertyDialog$Companion.make(PropertyDialog.kt:163)
        ... 102 more

Cannot use a property if it isn't used in the "config" session

Hi,

I have a property that is used on the controller of other property, like this:

    define("baseDir") {
      defaultValue = "/default/path"
    }
    define("targetPath") {
      enabled = false
      controller {
        value = other("baseDir").value + '/' + other("artifactId").value
      }
    }

The properity "baseDir" isn't used the "config" session, just in the "properties" session.

When I try to run "fork", I get the error Property named 'baseDir' does not exist. This always happen with properties that aren't used in the "config" session. Is there any way to override this behaviour?

Regards,

Rodrigo

Bubble up property validation message

image

just "missing or invalid" message is too generic. if possible propagate detailed error message to the exception so be able to easier demystify problems

Introduce `-Pfork.execute` flag

which will be useful for cases when changes from updated TPL need to be applied without updating values already saved on GUI dialog

Encrypt password properties

Design :

  1. implement and use obfuscated (by proguard https://www.guardsquare.com/en/products/proguard ) library that will return hashed encryption passphrase basing on project specific gradle build model details (do not share unobfuscated source code on GH)
  2. provide method fork.property(name) that will just proxy project.findProperty(name) or if property is encrypted (basing on definition #9 #5 ) decrypted value instead of as is (logic similar to https://github.com/etiennestuder/gradle-credentials-plugin ; encrypter with passphrase based on 1) )

sample usage

plugins {
    id ("com.neva.fork")
}

repositories {
    maven { 
        url = uri("http://nexus.xxx.com/groups/private")
        username = fork.prop("nexus.username")
        password = fork.prop("nexus.password")
    }
}

Rule for deleting lines between tags

as of fork config should delete forking related gradle files and contents, dedicated rules for deleting e.g

build.gradle.kts

// fork:start
fork {
    properties {
        /// this lines should not be available in forked project
    }
}
// fork:end

would be nice

Providers for default value, control bugfix

  1. default value should be a callback called as late as it is possible
  2. when controller {} callback is used to assign other field value; such other field value cannot be then changed

Post-fork commands

Hi,

Is it possible to execute a command line script inside the forked app's folder, like a git repo initialization? I could do something like fork.finalizedBy gitInit but I don't know how to get the generated folder path.

I also tried the following in 'fork.config' but it didn't work:

action(Action { Runtime.getRuntime().exec("git init") })

Could you help me?

Thanks in advance. Regards!

Properties precedence

the last one has always-win precedence...

expected:

  1. gradle.properties
  2. gradle.user.properties
  3. CMD line -Pkey=value

actual:

  1. gradle.user.properties
  2. gradle.properties
  3. CMD line

actual when setting fork.override=true

  1. gradle.properties
  2. CMD line
  3. gradle.user.properties

Providers for default value, control bugfix

  1. default value should be a callback called as late as it is possible
  2. when controller {} callback is used to assign other field value; such other field value cannot be then changed

Autodecrypt passwords specified in own / user specific properties file

Make Fork decrypt password, so other plugins like GAP don't have to use forkProps extension to pass properties.

When fork generates properties file with password encrypted it adds fp: prefix to them:

fileTransfer.sftp.user=username
fileTransfer.sftp.password={fp:nWVIC40MKSf2ZasfflkOXA==}

knowing the peb template it can decode values on the fly, so build script author don't have to pass through in the build file:

aem {
    fileTransfer {
        sftp {
            user = forkProps["fileTransfer.sftp.user"]
            password = forkProps["fileTransfer.sftp.password"]
        }
    }
}

Introduce more content manipulating actions

Imagine e.g

fork {
    config {
        cloneFiles()
        // ...
        removeLine("apply from(\"gradle/fork.gradle.kts\")", "/build.gradle.kts") // new action
        removeFile("gradle/fork.gradle.kts") // new action
        copyTemplateFile("README.MD") // will override existing
    }
}

to be able to clean up forking configuration (fork will not be forkable)

Define a task validating existence of user-specific properties file

Imagine

tasks {
    instanceSetup {
        dependsOn("propsCheck")
    }
}

right now, after cloning a project, we could forget about generating user-specific configuration to run some task

maybe we could introduce separate plugin e.g com.neva.fork.props.check with will allow to run only tasks and props task at first, then after generating gradle.user.properties all others...

Introduce predefined settings task

Imagine

sh gradlew fork -Pfork.config=properties

replaced by just

sh gradlew properties

which will use reserved in-place config named properties

Ability to define default values for properties defined multiple times

There is no way to elegantly define default value for e.g projectGroup. Consider new DSL:

fork {
    config {
        cloneFiles()
        moveFiles([
                "/com/company/x/example": "/{{projectGroup|substitute('.', '/')}}/{{projectName}}",
                "/example": "/{{projectName}}"
        ])
        replaceContents([
                "com.company.x.example": "{{projectGroup}}.{{projectName}}",
                'com.company.x': "{{projectGroup}}",
                "Example": "{{projectLabel}}",
                "example": "{{projectName}}",
        ])
        defineProperties([
                property "projectGroup", "com.company.x"
                property "projectLabel", "Example",
                property "projectName", "example" 

                /* below mostly expected :)
                property "projectName",  { 
                    defaultValue = "example"
                    validator = { !it.value('projectGroup').contains(it.value)) } 
                }
                */
               property "libVersion", { // that property will be used in some template file, not in above sections directly
                    defaultValue = "1.0.0"
                    radio ['1.0.0', '2.00', '3.0.0''] // GUI control type 'radio' instead of default 'textfield'
                    required()
                }
        ])
    }
}

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.