Task-Script (NoJSON-Based Project Management Light Frame)
当前版本 v0.1.1
新特性:
增加模板字符串作为字面量参数类型,可以内嵌引用和二级引用 例:`${name} is good,${Father.name} must be remembered!`
历史版本
v0.1.0
|- 实现nojson范式
|- 支持Task的解析和执行(同步)
|- 实现 autoparse 字段 :将域内的字段优先解析成Task类型
|- 实现 functionalized 字段:将当前Task结构型转换成函数
|- 实现 relocation 字段:对以./和../开头的字符串类型进行__dirname填充 重定位到当前执行文件的目录
|- 支持 require 和 exports 字段
|- 实现 factorymode 字段:立即执行的工厂模式
|- 支持以命令行方式执行.json结构的task-script文件
路线图
5月底
|- 支持后置赋值语句 b=:a(b赋值给a)
|- 支持 tagswitch标签字段和tagdeparted分片字段 并实现按标签开启和屏蔽action代码以及文件的拆分和合并
6月中旬
|— 支持 asynchronized 字段,异步处理子任务
6月底-7月初
|- 添加文件函数:1.编译期在require字段中引入外部js或json路径2.运行期动态加载外部Task
7月中旬
|- 支持 arrowfunction 字段,直接解析箭头函数
8月
|- 语法支持条件分支 例:[条件](满足条件执行task1)(不满足条件执行task2) 或 [条件1][条件2]同时满足条件1、2执行task1
9月底
|- 支持 命令行将js(es5/es6)以文件为单位一键转换成 task-script 的json格式。
12月底
|- java 版本的实现
18年初
|- task 仓库
18年中
|- task-script 可视化编辑器
1. Task-Script 简介
介绍Task-Script前先奉上 HelloWorld 程序!
{
"require":["console"],
"action":[
"console.log('Hello World!')"
]
}
Task-Script不是一门语言,它把语言中表达逻辑的部分跟表达描述的部分一视同仁地塞到json字段中,它是遵从nojson(Not Only JSON http://www.nojson.com )范式的一种实现。
它按照一定的约定将逻辑代码存储在action字段内,并辅以require、exports来进行输入输出;task来进行内外域构建,除了约定的关键字,{...}
内的其余字段皆为 配置即声明 的引用,用于编写期
或运行期
装载数据。
脱离底层技术的实现,它就是上层的业务说明书,并具有json数据易于传输的特性。
它是一种重新组织业务结构的轻量级框架,目前为止node仅仅作为最方便的技术实现之一。
通常我们见到的工程由js
文件和json
文件构成(或许还有.java
和.xml
, .m
和.plist
等等),js负责业务逻辑实现,json负责静态数据配置。
这种搭配满足了数据驱动业务的软件开发模型:数据是预先定义的规范,业务逻辑作为实现,在系统部署后就变化不大(即便有变化也会以升级的形态出现),异构的系统间由数据规范来对接。
目前为止这种模式的开发已经遇到了发展瓶颈:大量的同质化框架只能比拼性能;或者在某个领域内的专精;以及如何采用更加易用的API和调用方式来亲近开发者。
但是面对本来就经常变化的需求,换言之,本质就是经常改动的需求,目前人类所采用的方式是————大数据和高强度的训练。这种方式产生的"智能"的问题是————他们所得出的结果只能在人类设计思维的框架内达到优质、稳定和可用。 而创造性的工作没有明确的目标和规范,就像乐高积木,使用已知的规则允许无意识的天马行空的组合,再从结果中筛选出想要的结果。 Task-Script作为"桌游开放平台"终极语言nojson的先锋队,首先将桌游程序依赖于开发人员的关系翻转并希冀大幅革命现有逻辑。
任意一种编程语言在面对高度增长变化的需求时,以其现有的架构应对起来消耗都是惊人的(系统维护、二次开发、迭代),而task-script致力于解决于此。
以下是使用Task-Script的8点优势:
1.使用task-script管理node工程,可以将经常变化的逻辑代码抽离成 `.json` 的action字段,根据2-8原则,高复用率的代码抽离成平台(当前为node)接口,两种逻辑代码分开维护。
2.由业务组织文件结构,每一个独立的任务为`.json`文件,如Test示例中`cleanBeforeTest.json`专门用于清理测试文件夹(由平台和能力组织底层调用的支撑接口),使得架构更加清晰(技术为业务而服务)。
3.函数式编程思想,`action`字段仅仅组装逻辑元件,数据来自于配置本身,其组装逻辑本身也是外部输入数据,可被其他Task修改。
4.依赖反转,没有类型概念无需new对象,json文件即对象,解析后自动注入所有依赖,运行期间任意修改数据,即时生效无需编译和部署。
5.向下兼容,使用json通用格式,可以把逻辑代码作为普通数据处理,非常容易结合可视化工具(后续推出)操作。
6.模块化和自包含,`require`和`exports`作为每一个任务输入输出的管道,内置Task类型可以和平台`函数`无缝转换,支持`匿名函数`,`立即执行函数`和引用`外部函数`和`文件函数`。
7.面向切面编程,可以将一份逻辑代码根据标签(TAG)拆分成若干,以便根据使用场景动态切换调试模式,部署模式或自定义模式等(运行时自动合并)。
8.高度灵活,能够整合现有技术的最佳实践,支持命令行和npm,便于与现有系统集成。
2.快速上手
2.1 准备工作
安装node npm 并全局安装 task-script
npm install -g task-script
2.2 创建一个示例工程
使用 tsk
命令创建一个演示工程,默认在当前工作目录下创建Test文件夹
tsk create_test <yourTestDir>
2.3 执行生成用户工程任务
从包名为com.test的HelloWorld安卓模板项目 生成一个用户自定义包名(com.world.ofmine)的项目
tsk genFromTemplate.json
2.4 立即修改并保存
演示项目并没有完成所有功能,你可以添加自己的后续实现以及一些log语句并再次执行以上语句,记得在修改前调用如下清理任务
tsk cleanBeforeTest.json
若你直接在示例项目中添加了一些自己的文件,请将其加入 cleanBeforeTest.json
的include
清单,它看起来如下:
{...
"include": [
"public",
".cleanBeforeTest",
".genFromTemplate",
"ReadMe.md",
"HelloWorld",
"cleanBeforeTest.json",
"genFromTemplate.json"
],...
}
注:使用命令行执行任务会在其执行文件{name}.json同级目录下生成.{name}
的目录,该目录下存放该命令的执行依赖
可以使用如下命令格式配置或修改参数,也可以直接编辑 .{name}/require.json
tsk <taskScriptFile> { --<key> value | --<key>-<type> value }
使用-- 指定的键值都为字符串 若想指定文件类型(作为外部依赖被require) 请指定file类型 ---file
例:
tsk Test/cleanBeforeTest.json --Fs-file public/Fs.js --Path-file public/Path.js --console-file public/console.js --testDir Test
注:若指定过依赖则下一次调用不需要再次指定,指定增加或修改的依赖参数即可
tsk Test/cleanBeforeTest.json --testDir MyTest
3.Task类型
若你希望把已有的程序拿一部分逻辑放在json中,那么推荐你以引用的方式使用Task类。
var Task = require("task-script").Task;
//把一些require字段需要的基本依赖传入
new Task()
.openSync(taskScriptFile)//你要执行的json文件
.execSync({Fs:require('fs'),Path:require('path'),console:console});
或者像命令行一样自动加载其依赖
var RequirementManager = require("task-script").RequirementManager;
var requireParamsWrapper = {}//需要添加或覆盖的参数字段
mountedRequirment = RequirementManager.requireDir(requireDir)//需要手动指定依赖文件夹,根据taskScriptFile路径拼接(去掉`.json`前面添加`.`)
.loadRequireConfig()//从文件读取参数
.saveConfig(requireParamsWrapper)
.mountModule();//挂载文件类型的依赖
new Task()
.openSync(yourFilePath)
.execSync(mountedRequirment);
Task类型采用鸭式辩形来识别 一个典型的Task类型具有如下属性:
1. name 任务名称,用于debug显示和引用,匿名函数可以没有
2. action 要执行的语句,按数组顺序执行
3. task 子任务,根据synchronized字段,按照数组同步顺序或异步并行执行
其他具有功能性的属性,可直接在task.json文件中配置,解析时生效
relocation = false;//若设置为true,则当前及孩子Task域内的【路径】字段重定位到当前task文件路径 【】
asynchronized = false;//如设置为true,则子task间是异步并行执行的
autoparse = false;//如果设置为true,会把当前及孩子Task域内的对象字段优先解析成Task实体
functionalized = false;//如设置为true,则会将当前Task实体解析成函数(需要autoparse作为前提)
arrowfunction = false;//如设置为true,则支持箭头函数语法
factorymode = false;//如设置为true,则把当前Task立即运行并返回运行结果
debuggable = false;//如果设置为true,则会打印当前Task及其子Task的所有Log
序列化的Task和反序列化的Task差异不大,直接在其Object作用块内属性赋值,但以上功能性关键字无法被赋值上
Task-Script 演示工程
使用task-script将一个 HelloWorld 模板工程修改成根据用户输入参数预处理生成的用户工程
用户输入生成的工程名和要修改的包名
模板工程结构如下:
|- src
|- com.test
|- utils
|- Console.java
|- HelloWorld.java
|- org.world
|- OtherWorld.java
以用户输入目标工程名<即路径> <projectPath>
=./MyWorld 和目标包名 <packageName>
=com.world.ofmine 为例
需要修改项目名称 HelloWorld 为 MyWorld 且 包名 com.test 改成 com.world.ofmine 预期修改后用户工程结构如下:
|- src
|- org.world
|- ofmine
|- utils
|- Console.java
|- MyWorld.java
|- OtherWorld.java
根据人工修改的顺序把任务细分为如下:
1.拷贝模板项目 CopyProject
核心语句
Fs.copy(path,projectPath)
我们将当前作为模板的 HelloWorld 文件夹整体拷贝到用户给定的projectPath
2.修改包名 ModifyPackageName
手动修改包名我们会做两个操作,且这两个操作谁先谁后并不重要
1)根据包名结构创建新的路径,把原路径下的所有源文件拷贝到新路径下,删除原路径
2)遍历所有文件,若引用了原路径com.test则全部批量修改成新路径
但是作为自动化工具必须面临一个问题:
若是先移动了目标包名路径结构下的文件则会影响下一个流程,需要到新移动的位置去修改原文件,而新移动的位置依赖于外部输入,并不直观(我们需要task-script脚本自身能够起到直观描述业务的能力)
所以我们优先做文件内容替换,将所引用的包名替换成新的包名,这个操作没有依赖也不被后续的流程所依赖。
2.1 内容替换 ReplaceContent
从全局看,修改包名这个业务会产生两个重要的影响:
1) 所依赖的类,若出现了以`包名.类名`的全路径引用,需要替换包名部分。
2) 单独出现的包名,主要在java文件的import部分或者AndroidManifest.xml的package字段,整体替换
但是无论是哪种情况都可以通过正则替换完成。
核心语句
Fs.readAsString(path).replace(regexp,packageName).save(path)
2.2 移动包 MovePackage
核心语句
// 将形如 org.world.ofmine 的包名形式改成形如 org/world/ofmine 的包路径形式
packagePath=packageName.split('.').join(Path.sep)
// baseDir拼接(__dirname为当前文件所在文件夹路径)
"baseDir=Path.resolve(__dirname,projectPath,baseDir)",
// 拼接目标路径 并存储在全局任务中供后续任务使用
moveToPath=Path.join(baseDir,packagePath)
// 移动包
Fs.move(moveFromPath,moveToPath)
// 直接配置 moveFromPath 和 baseDir 的参数值
{ "name":"MovePackage" ... "moveFromPath":"com/test","baseDir":"src" }
// 声明moveToPath到全局变量并用于后续任务的使用
{ "name":"genFromTemplate" ... "moveToPath":"" }
3 修改程序名称 ModifyAppName
此部分尚未实现,用户可以手动改写genFromTemplate.json,添加名为ModifyAppName的任务
3.1 获得程序名称
由于并没有提供程序名称的直接输入,首先需要从用户提供的目标路径中提取程序名,以供后续步骤使用
3.2 文件内容引用替换
下一步是将程序中所有文件内引用的HelloWorld替换为程序名(需要判断是关键字还是字符串)