├── examples ├── cargo-bc.png ├── subdomain.png ├── cargo-problem.png ├── step2-problem.png ├── step2-Java │ ├── domain │ │ ├── Provider.java │ │ ├── ValueObject.java │ │ ├── Entity.java │ │ ├── AggregateRoot.java │ │ ├── ValueObjectC.java │ │ ├── ValueObjectD.java │ │ ├── EntityB.java │ │ ├── AggregateRootB.java │ │ ├── Router.java │ │ └── AggregateRootA.java │ ├── repositories │ │ ├── Repository.java │ │ └── AggregateRootARepo.java │ ├── gateways │ │ └── FakeRouter.java │ └── Main.java ├── bc-code │ ├── src │ │ ├── gateways │ │ │ └── gateway.cpp │ │ ├── interface │ │ │ └── cargo_api.cpp │ │ ├── repositories │ │ │ └── repository.cpp │ │ ├── services │ │ │ └── service.cpp │ │ └── domain │ │ │ └── model.cpp │ ├── test │ │ ├── test_main.cpp │ │ └── app_test.cpp │ ├── include │ │ ├── gateways │ │ │ ├── msg.h │ │ │ └── gateway.h │ │ ├── interface │ │ │ ├── msg.h │ │ │ └── api.h │ │ ├── repositories │ │ │ └── repository.h │ │ ├── domain │ │ │ └── model.h │ │ └── services │ │ │ └── service.h │ ├── CMakeLists.txt │ └── main.cpp ├── step3-code │ ├── main.cpp │ └── code.h ├── inheritance-tree-code │ ├── main.cpp │ └── code.h ├── subdomain-code │ ├── main.cpp │ └── code.h ├── step2-code │ ├── main.cpp │ └── code.h ├── step1-problem.dot ├── cargo-problem.dot ├── inheritance-tree.dot ├── step2-problem.dot ├── step1-code │ └── code.h ├── subdomain.dot └── cargo-bc.dot ├── scripts ├── build.sh └── build-linux.sh ├── viz ├── tq_viz_cli │ ├── main.go │ └── cmd │ │ ├── call.go │ │ ├── java_code.go │ │ ├── root.go │ │ ├── include.go │ │ ├── utils.go │ │ ├── db_dep.go │ │ ├── tar.go │ │ ├── java_db.go │ │ ├── coll.go │ │ ├── db_chain.go │ │ └── sql_parse.go ├── README.md ├── viz_suite_test.go ├── merge_viz.go ├── coll_viz.go ├── package.go ├── filter_test.go ├── viz_test.go ├── table.go ├── filter.go ├── pkg.go └── incl_viz.go ├── dot ├── dot_suite_test.go ├── dot_test.go ├── doxygen.go └── test.dot ├── tequila_suite_test.go ├── doxygen.sh ├── main.go ├── feature.md ├── .gitignore ├── tequila_bc_test.go ├── README.md ├── LICENSE ├── model ├── common.go ├── problem.go └── solution.go ├── dot.go ├── doxygen.go └── tequila_test.go /examples/cargo-bc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newlee/tequila/HEAD/examples/cargo-bc.png -------------------------------------------------------------------------------- /examples/subdomain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newlee/tequila/HEAD/examples/subdomain.png -------------------------------------------------------------------------------- /examples/cargo-problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newlee/tequila/HEAD/examples/cargo-problem.png -------------------------------------------------------------------------------- /examples/step2-problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newlee/tequila/HEAD/examples/step2-problem.png -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | go install github.com/newlee/tequila/viz/tq_viz_cli 4 | 5 | -------------------------------------------------------------------------------- /examples/step2-Java/domain/Provider.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class Provider { 5 | 6 | 7 | } -------------------------------------------------------------------------------- /examples/step2-Java/domain/ValueObject.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class ValueObject { 5 | 6 | 7 | } -------------------------------------------------------------------------------- /examples/step2-Java/domain/Entity.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class Entity { 5 | 6 | public int id; 7 | 8 | } -------------------------------------------------------------------------------- /examples/step2-Java/domain/AggregateRoot.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | public class AggregateRoot extends Entity { 4 | 5 | 6 | } -------------------------------------------------------------------------------- /scripts/build-linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | GOOS=linux GOARCH=amd64 go build github.com/newlee/tequila/viz/tq_viz_cli 4 | 5 | -------------------------------------------------------------------------------- /examples/step2-Java/domain/ValueObjectC.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class ValueObjectC extends ValueObject { 5 | 6 | 7 | } -------------------------------------------------------------------------------- /examples/step2-Java/domain/ValueObjectD.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class ValueObjectD extends ValueObject { 5 | 6 | 7 | } -------------------------------------------------------------------------------- /examples/step2-Java/repositories/Repository.java: -------------------------------------------------------------------------------- 1 | package repositories; 2 | 3 | import domain.*; 4 | 5 | 6 | public class Repository { 7 | 8 | } -------------------------------------------------------------------------------- /viz/tq_viz_cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/newlee/tequila/viz/tq_viz_cli/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /examples/step2-Java/domain/EntityB.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class EntityB extends Entity { 5 | 6 | private ValueObjectD vo_d; 7 | } -------------------------------------------------------------------------------- /examples/bc-code/src/gateways/gateway.cpp: -------------------------------------------------------------------------------- 1 | #include "gateways/gateway.h" 2 | 3 | using namespace gateways; 4 | 5 | void CargoProviderImpl::Confirm(Cargo *cargo) 6 | { 7 | 8 | } -------------------------------------------------------------------------------- /examples/step2-Java/domain/AggregateRootB.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class AggregateRootB extends AggregateRoot { 5 | 6 | private AggregateRootA a; 7 | 8 | } -------------------------------------------------------------------------------- /examples/bc-code/test/test_main.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | int main(int argc, char** argv) { 4 | ::testing::InitGoogleTest(&argc, argv); 5 | return RUN_ALL_TESTS(); 6 | } -------------------------------------------------------------------------------- /examples/step2-Java/domain/Router.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class Router extends Provider { 5 | 6 | public int select() { 7 | return 0; 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /examples/bc-code/include/gateways/msg.h: -------------------------------------------------------------------------------- 1 | #ifndef BC_DEMO_GW_MSG_H 2 | #define BC_DEMO_GW_MSG_H 3 | 4 | namespace gateways { 5 | struct CargoCreatedMsg { 6 | int Id; 7 | }; 8 | } 9 | #endif //BC_DEMO_GW_MSG_H 10 | -------------------------------------------------------------------------------- /examples/step2-Java/gateways/FakeRouter.java: -------------------------------------------------------------------------------- 1 | package gateways; 2 | 3 | import domain.*; 4 | 5 | 6 | public class FakeRouter extends Router { 7 | public int select(){ 8 | System.out.println("routed \n"); 9 | return 1; 10 | }; 11 | } -------------------------------------------------------------------------------- /dot/dot_suite_test.go: -------------------------------------------------------------------------------- 1 | package dot_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestDot(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Dot Suite") 13 | } 14 | -------------------------------------------------------------------------------- /viz/README.md: -------------------------------------------------------------------------------- 1 | ## How to use? 2 | ### Prepare 3 | * install golang 4 | * set env variable: GOPATH 5 | 6 | ### Build & Run 7 | * build: 8 | `./scripts/build.sh` 9 | * cross build: 10 | `./scripts/build-linux.sh` 11 | * run & view help: 12 | `tq_viz_cli` -------------------------------------------------------------------------------- /viz/viz_suite_test.go: -------------------------------------------------------------------------------- 1 | package viz_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestViz(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Viz Suite") 13 | } 14 | -------------------------------------------------------------------------------- /examples/bc-code/include/interface/msg.h: -------------------------------------------------------------------------------- 1 | #ifndef BC_DEMO_INTERFACE_MSG_H 2 | #define BC_DEMO_INTERFACE_MSG_H 3 | 4 | namespace api { 5 | struct CreateCargoMsg 6 | { 7 | int Id; 8 | int AfterDays; 9 | }; 10 | 11 | } 12 | #endif //BC_DEMO_INTERFACE_MSG_H 13 | -------------------------------------------------------------------------------- /tequila_suite_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestTequila(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Tequila Suite") 13 | } 14 | -------------------------------------------------------------------------------- /doxygen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | doxygen examples/inheritance-tree-code/Doxyfile 3 | doxygen examples/step1-code/Doxyfile 4 | doxygen examples/step2-code/Doxyfile 5 | doxygen examples/step2-Java/Doxyfile 6 | doxygen examples/step3-code/Doxyfile 7 | doxygen examples/subdomain-code/Doxyfile 8 | -------------------------------------------------------------------------------- /examples/step3-code/main.cpp: -------------------------------------------------------------------------------- 1 | #include "code.h" 2 | 3 | using namespace Gateways; 4 | using namespace Repositories; 5 | 6 | int main(int argc, char const *argv[]) 7 | { 8 | router = new FakeRouter(); 9 | AggregateRootB* b = new AggregateRootB(); 10 | b->Init(); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /examples/inheritance-tree-code/main.cpp: -------------------------------------------------------------------------------- 1 | #include "code.h" 2 | 3 | using namespace Gateways; 4 | using namespace Repositories; 5 | 6 | int main(int argc, char const *argv[]) 7 | { 8 | router = new FakeRouter(); 9 | AggregateRootB* b = new AggregateRootB(); 10 | b->Init(); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /examples/step2-Java/repositories/AggregateRootARepo.java: -------------------------------------------------------------------------------- 1 | package repositories; 2 | 3 | import domain.*; 4 | 5 | 6 | public class AggregateRootARepo extends Repository { 7 | private AggregateRootA[] arList; 8 | public void save(AggregateRootA a){ 9 | System.out.println("saved\n"); 10 | }; 11 | } -------------------------------------------------------------------------------- /examples/subdomain-code/main.cpp: -------------------------------------------------------------------------------- 1 | #include "code.h" 2 | 3 | using namespace subdomain1::Gateways; 4 | using namespace subdomain1::Repositories; 5 | 6 | int main(int argc, char const *argv[]) 7 | { 8 | router = new FakeRouter(); 9 | AggregateRootA* a = new AggregateRootA(); 10 | a->Init(); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | dotFile := "examples/cargo-problem.dot" 9 | dddModel := ParseProblemModel(dotFile) 10 | 11 | codeDir := "examples/bc-code/html" 12 | codeModel := ParseCodeProblemModel(codeDir, make([]string, 0)) 13 | fmt.Println(dddModel.Compare(codeModel)) 14 | } 15 | -------------------------------------------------------------------------------- /examples/bc-code/include/gateways/gateway.h: -------------------------------------------------------------------------------- 1 | #ifndef BC_DEMO_GATEWAY_H 2 | #define BC_DEMO_GATEWAY_H 3 | 4 | #include "services/service.h" 5 | 6 | namespace gateways { 7 | using namespace services; 8 | 9 | struct CargoProviderImpl: CargoProvider 10 | { 11 | virtual void Confirm(Cargo *cargo) override; 12 | }; 13 | } 14 | #endif //BC_DEMO_GATEWAY_H 15 | -------------------------------------------------------------------------------- /examples/step2-code/main.cpp: -------------------------------------------------------------------------------- 1 | #include "code.h" 2 | 3 | using namespace Gateways; 4 | using namespace Repositories; 5 | 6 | int main(int argc, char const *argv[]) 7 | { 8 | // routerFactory.setRouter(new FakeRouter()); 9 | router = new FakeRouter(); 10 | AggregateRootARepo *repo = new AggregateRootARepo(); 11 | repo->Save(new AggregateRootA()); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /examples/step2-Java/domain/AggregateRootA.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | 4 | public class AggregateRootA extends AggregateRoot { 5 | 6 | private EntityB entity_b; 7 | private ValueObjectC vo_c; 8 | private Router router; 9 | public AggregateRootA(Router router) { 10 | this.router = router; 11 | } 12 | public void init() { 13 | router.select(); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /examples/step2-Java/Main.java: -------------------------------------------------------------------------------- 1 | import domain.*; 2 | import gateways.*; 3 | import repositories.*; 4 | 5 | 6 | public class Main { 7 | 8 | public static void main(String[] args) { 9 | System.out.println("main"); 10 | Router router = new FakeRouter(); 11 | AggregateRootARepo repo = new AggregateRootARepo(); 12 | repo.save(new AggregateRootA(router)); 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/bc-code/include/repositories/repository.h: -------------------------------------------------------------------------------- 1 | #ifndef BC_DEMO__REPO_H__ 2 | #define BC_DEMO__REPO_H__ 3 | 4 | #include "domain/model.h" 5 | //#include 6 | 7 | namespace repositories { 8 | using namespace domain; 9 | 10 | struct Repository 11 | { 12 | 13 | }; 14 | 15 | struct CargoRepository: Repository 16 | { 17 | CargoRepository(); 18 | void Save(Cargo* cargo); 19 | Cargo* FindById(int id); 20 | }; 21 | 22 | } 23 | 24 | #endif -------------------------------------------------------------------------------- /examples/bc-code/include/interface/api.h: -------------------------------------------------------------------------------- 1 | #ifndef BC_DEMO_API_H 2 | #define BC_DEMO_API_H 3 | 4 | #include "services/service.h" 5 | #include "msg.h" 6 | 7 | namespace api { 8 | using namespace services; 9 | 10 | struct Api { 11 | Api(std::shared_ptr); 12 | void CreateCargo(CreateCargoMsg* msg); 13 | void Delay(int cargoId, int days); 14 | 15 | private: 16 | std::shared_ptr cargoService_; 17 | }; 18 | } 19 | #endif //BC_DEMO_API_H 20 | -------------------------------------------------------------------------------- /examples/bc-code/src/interface/cargo_api.cpp: -------------------------------------------------------------------------------- 1 | #include "interface/api.h" 2 | 3 | using namespace api; 4 | using namespace services; 5 | 6 | Api::Api(std::shared_ptr cargoService) 7 | :cargoService_(cargoService) 8 | { 9 | } 10 | 11 | void Api::CreateCargo(CreateCargoMsg * msg) 12 | { 13 | this->cargoService_->Create(msg->Id,msg->AfterDays); 14 | } 15 | 16 | void Api::Delay(int cargoId, int days) { 17 | this->cargoService_->Delay(cargoId,days); 18 | } 19 | -------------------------------------------------------------------------------- /examples/step1-problem.dot: -------------------------------------------------------------------------------- 1 | digraph g { 2 | node[style = filled, color = orange]; 3 | 4 | AggregateRootA[label="AggregateRootA\l", comment=AR, shape=box] 5 | EntityB[label="EntityB\l", comment=E, shape=box] 6 | ValueObjectC[label="ValueObjectC\l", comment=VO, shape=box, color=lightblue] 7 | ValueObjectD[label="ValueObjectD\l", comment=VO, shape=box, color=lightblue] 8 | AggregateRootA -> EntityB 9 | AggregateRootA -> ValueObjectC 10 | EntityB -> ValueObjectD 11 | } -------------------------------------------------------------------------------- /examples/bc-code/src/repositories/repository.cpp: -------------------------------------------------------------------------------- 1 | #include "repositories/repository.h" 2 | 3 | using namespace repositories; 4 | 5 | std::vector cargo_list; 6 | 7 | CargoRepository::CargoRepository() 8 | { 9 | } 10 | 11 | void CargoRepository::Save(Cargo* cargo) 12 | { 13 | cargo_list.push_back(cargo); 14 | } 15 | 16 | Cargo* CargoRepository::FindById(int id) 17 | { 18 | for(Cargo* cargo : cargo_list){ 19 | if(cargo->getId() == id) { 20 | return cargo; 21 | } 22 | } 23 | 24 | return NULL; 25 | } 26 | -------------------------------------------------------------------------------- /examples/cargo-problem.dot: -------------------------------------------------------------------------------- 1 | digraph g { 2 | node[style = filled, color = orange]; 3 | 4 | Cargo[label="Cargo\l", comment=AR, shape=box] 5 | Delivery[label="Delivery\l", comment=VO, shape=box, color=lightblue] 6 | Product[label="Product\l", comment=VO, shape=box, color=lightblue] 7 | CargoRepository[label="CargoRepository", comment=Repo, shape=box, color=yellow] 8 | CargoProvider[label="CargoProvider", comment=Provider, shape=box, color=purple] 9 | Cargo -> Delivery 10 | Cargo -> Product 11 | CargoRepository -> Cargo[style="dashed"] 12 | } -------------------------------------------------------------------------------- /examples/inheritance-tree.dot: -------------------------------------------------------------------------------- 1 | digraph g { 2 | node[style = filled, color = orange]; 3 | 4 | AggregateRootA[label="AggregateRootA\l", comment=AR, shape=box] 5 | AggregateRootB[label="AggregateRootB\l", comment=AR, shape=box] 6 | EntityB[label="EntityB\l", comment=E, shape=box] 7 | EntityC[label="EntityB\l", comment=E, shape=box] 8 | ValueObjectC[label="ValueObjectC\l", comment=VO, shape=box, color=lightblue] 9 | ValueObjectD[label="ValueObjectD\l", comment=VO, shape=box, color=lightblue] 10 | AggregateRootA -> EntityB 11 | AggregateRootA -> EntityC 12 | AggregateRootA -> ValueObjectC 13 | EntityB -> ValueObjectD 14 | } -------------------------------------------------------------------------------- /feature.md: -------------------------------------------------------------------------------- 1 | 2 | ## Core Element 3 | ### Package 4 | * structure slice (ref) 5 | 6 | ### structure 7 | * full name (package name + self name) as identify 8 | * property (native type) 9 | * other structure reference (full name / identity) 10 | * method slice (full name / identity) 11 | 12 | ### method 13 | * full name / identity (??) 14 | * parameters slice (aggregate) 15 | * name 16 | * type 17 | * structure dependency 18 | * full name 19 | * method 20 | * property (contains other structure reference), maybe have cascade like a.b.c.d or a->b->c->d 21 | * return value 22 | * name and type (some language support more than one return value) -------------------------------------------------------------------------------- /examples/bc-code/src/services/service.cpp: -------------------------------------------------------------------------------- 1 | #include "services/service.h" 2 | 3 | using namespace services; 4 | 5 | 6 | void CargoService::Create(int id, int days) 7 | { 8 | Delivery* delivery = new Delivery(days); 9 | Cargo* cargo = new Cargo(delivery, id); 10 | cargo->AddProduct(1); 11 | this->cargoRepository_->Save(cargo); 12 | this->cargoProvider_->Confirm(cargo); 13 | } 14 | void CargoService::Delay(int id, int days) 15 | { 16 | Cargo* cargo = cargoRepository_->FindById(id); 17 | if(cargo != NULL) { 18 | cargo->Delay(days); 19 | cargoRepository_->Save(cargo); 20 | this->cargoProvider_->Confirm(cargo); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /viz/merge_viz.go: -------------------------------------------------------------------------------- 1 | package viz 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var MergeHeaderFunc = func(input string) string { 8 | tmp := strings.Split(input, ".") 9 | if len(tmp) > 1 { 10 | return strings.Join(tmp[0:len(tmp)-1], ".") 11 | } 12 | return input 13 | } 14 | 15 | var MergePackageFunc = func(input string) string { 16 | split := "/" 17 | if !strings.Contains(input, split) { 18 | split = "." 19 | } 20 | if !strings.Contains(input, split) { 21 | split = "::" 22 | } 23 | tmp := strings.Split(input, split) 24 | packageName := tmp[0] 25 | if packageName == input { 26 | packageName = "main" 27 | } 28 | 29 | if len(tmp) > Level { 30 | packageName = strings.Join(tmp[:(Level)], split) 31 | } 32 | 33 | return packageName 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | *.iml 7 | *.class 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 16 | .glide/ 17 | .idea/ 18 | 19 | html/ 20 | tequila 21 | 22 | *.gch 23 | 24 | .idea 25 | 26 | .DS_Store 27 | 28 | .vscode/ 29 | xml/ 30 | build/ 31 | viz/merged.dot 32 | viz/merged.svg 33 | viz/merged_package.dot 34 | viz/merged_package.svg 35 | viz/dep.dot 36 | viz/dep.svg 37 | dep.dot 38 | dep.svg 39 | CMakeCache.txt 40 | CMakeFiles/ 41 | CTestTestfile.cmake 42 | Makefile 43 | bc_demo 44 | bc_demo_test 45 | cmake_install.cmake 46 | -------------------------------------------------------------------------------- /dot/dot_test.go: -------------------------------------------------------------------------------- 1 | package dot_test 2 | 3 | import ( 4 | . "github.com/newlee/tequila/dot" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Dot", func() { 11 | Context("Parse api dot file", func() { 12 | It("api doxygen file", func() { 13 | node := ParseDoxygenFile("test.dot") 14 | 15 | Expect(node.Name).Should(Equal("api::Api")) 16 | serviceNode := node.DstNodes[0].Node 17 | Expect(serviceNode.Name).Should(Equal("services::CargoService")) 18 | 19 | var cargoNode *Node 20 | for _, node := range serviceNode.DstNodes[0].Node.DstNodes { 21 | if node.Node.Name == "domain::Cargo" { 22 | cargoNode = node.Node 23 | } 24 | } 25 | Expect(cargoNode).ShouldNot(Equal(nil)) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /examples/step2-problem.dot: -------------------------------------------------------------------------------- 1 | digraph g { 2 | node[style = filled, color = orange]; 3 | 4 | AggregateRootA[label="AggregateRootA\l", comment=AR, shape=box] 5 | AggregateRootB[label="AggregateRootB\l", comment=AR, shape=box] 6 | EntityB[label="EntityB\l", comment=E, shape=box] 7 | ValueObjectC[label="ValueObjectC\l", comment=VO, shape=box, color=lightblue] 8 | ValueObjectD[label="ValueObjectD\l", comment=VO, shape=box, color=lightblue] 9 | AggregateRootARepo[label="AggregateRootARepo", comment=Repo, shape=box, color=yellow] 10 | Router[label="Router", comment=Provider, shape=box, color=purple] 11 | AggregateRootA -> EntityB 12 | AggregateRootA -> ValueObjectC 13 | EntityB -> ValueObjectD 14 | AggregateRootB -> AggregateRootA[style="dashed"] 15 | AggregateRootARepo -> AggregateRootA[style="dashed"] 16 | } -------------------------------------------------------------------------------- /examples/bc-code/src/domain/model.cpp: -------------------------------------------------------------------------------- 1 | #include "domain/model.h" 2 | 3 | using namespace domain; 4 | 5 | int Entity::getId() 6 | { 7 | return id; 8 | } 9 | Cargo::Cargo(Delivery* delivery, int id) 10 | :delivery(delivery) 11 | { 12 | this->id = id; 13 | } 14 | Cargo::~Cargo() 15 | { 16 | 17 | } 18 | void Cargo::Delay(int days) 19 | { 20 | int after = this->delivery->AfterDays; 21 | this->delivery = new Delivery(after + days); 22 | } 23 | 24 | void Cargo::AddProduct(int productId) 25 | { 26 | Product* product = new Product(productId); 27 | this->product_list.push_back(product); 28 | } 29 | 30 | int Cargo::afterDays() 31 | { 32 | return this->delivery->AfterDays; 33 | } 34 | 35 | Delivery::Delivery(int afterDays) 36 | :AfterDays(afterDays) 37 | { 38 | 39 | } 40 | 41 | Product::Product(int id) 42 | :productId(id) 43 | { 44 | } -------------------------------------------------------------------------------- /examples/bc-code/include/domain/model.h: -------------------------------------------------------------------------------- 1 | #ifndef BC_DEMO__MODEL_H__ 2 | #define BC_DEMO__MODEL_H__ 3 | 4 | #include 5 | 6 | namespace domain{ 7 | struct Entity 8 | { 9 | int getId(); 10 | protected: 11 | int id; 12 | }; 13 | 14 | struct AggregateRoot: Entity 15 | { 16 | 17 | }; 18 | 19 | struct ValueObject 20 | { 21 | 22 | }; 23 | 24 | struct Delivery: ValueObject 25 | { 26 | Delivery(int); 27 | int AfterDays; 28 | }; 29 | 30 | struct Product: ValueObject 31 | { 32 | Product(int id); 33 | private: 34 | int productId; 35 | }; 36 | 37 | struct Cargo: AggregateRoot 38 | { 39 | Cargo(Delivery*, int); 40 | ~Cargo(); 41 | void Delay(int); 42 | void AddProduct(int); 43 | int afterDays(); 44 | private: 45 | Delivery* delivery; 46 | std::vector product_list; 47 | }; 48 | 49 | struct CargoDelayed { 50 | int CargoId; 51 | }; 52 | } 53 | #endif -------------------------------------------------------------------------------- /examples/bc-code/include/services/service.h: -------------------------------------------------------------------------------- 1 | #ifndef BC_DEMO_SERVICE_H 2 | #define BC_DEMO_SERVICE_H 3 | 4 | #include "repositories/repository.h" 5 | 6 | namespace services { 7 | using namespace repositories; 8 | 9 | struct Provider 10 | { 11 | 12 | }; 13 | 14 | struct CargoProvider : Provider { 15 | virtual void Confirm(Cargo* cargo){}; 16 | }; 17 | 18 | struct CargoService { 19 | explicit CargoService(const std::shared_ptr cargoRepo, const std::shared_ptr cargoProvider) 20 | :cargoRepository_(cargoRepo) 21 | ,cargoProvider_(cargoProvider) 22 | { 23 | 24 | } 25 | void Create(int id, int days); 26 | void Delay(int id, int days); 27 | private: 28 | std::shared_ptr cargoRepository_; 29 | std::shared_ptr cargoProvider_; 30 | }; 31 | 32 | } 33 | #endif //BC_DEMO_SERVICE_H 34 | -------------------------------------------------------------------------------- /examples/step1-code/code.h: -------------------------------------------------------------------------------- 1 | class Entity 2 | { 3 | public: 4 | Entity(){}; 5 | ~Entity(){}; 6 | int Id; 7 | }; 8 | 9 | class AggregateRoot: public Entity 10 | { 11 | public: 12 | AggregateRoot(){}; 13 | ~AggregateRoot(){}; 14 | void Init(){ 15 | 16 | } 17 | }; 18 | 19 | 20 | class ValueObject 21 | { 22 | public: 23 | ValueObject(){}; 24 | ~ValueObject(){}; 25 | 26 | }; 27 | 28 | class ValueObjectC: public ValueObject 29 | { 30 | public: 31 | ValueObjectC(){}; 32 | ~ValueObjectC(){}; 33 | 34 | }; 35 | 36 | class ValueObjectD: public ValueObject 37 | { 38 | public: 39 | ValueObjectD(){}; 40 | ~ValueObjectD(){}; 41 | 42 | }; 43 | 44 | class EntityB: public Entity 45 | { 46 | public: 47 | EntityB(){}; 48 | ~EntityB(){}; 49 | ValueObjectD* vo_d; 50 | 51 | }; 52 | 53 | class AggregateRootA: public AggregateRoot 54 | { 55 | public: 56 | AggregateRootA(){}; 57 | ~AggregateRootA(){}; 58 | EntityB* entity_b; 59 | ValueObjectC* vo_c; 60 | }; -------------------------------------------------------------------------------- /tequila_bc_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | . "github.com/newlee/tequila" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Tequila", func() { 10 | Context("bc code compare", func() { 11 | It("problem domain", func() { 12 | dotFile := "examples/cargo-problem.dot" 13 | dddModel := ParseProblemModel(dotFile) 14 | 15 | codeDir := "examples/bc-code/html" 16 | codeModel := ParseCodeProblemModel(codeDir, make([]string, 0)) 17 | Expect(dddModel.Compare(codeModel)).Should(BeNil()) 18 | }) 19 | 20 | It("solution domain", func() { 21 | dotFile := "examples/cargo-bc.dot" 22 | bcModel := ParseSolutionModel(dotFile) 23 | 24 | Expect(len(bcModel.Layers)).Should(Equal(5)) 25 | 26 | codeDir := "examples/bc-code/html" 27 | codeModel := ParseCodeSolutionModel(codeDir, []string{"domain", "repositories", "gateways", "services", "api"}) 28 | 29 | Expect(bcModel.Compare(codeModel)).Should(BeNil()) 30 | }) 31 | 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/subdomain.dot: -------------------------------------------------------------------------------- 1 | digraph g { 2 | node[style = filled, color = orange]; 3 | 4 | subgraph cluster1 { 5 | label=subdomain1 6 | AggregateRootA[label="AggregateRootA\l", comment=AR, shape=box] 7 | AggregateRootB[label="AggregateRootB\l", comment=AR, shape=box] 8 | EntityB[label="EntityB\l", comment=E, shape=box] 9 | ValueObjectC[label="ValueObjectC\l", comment=VO, shape=box, color=lightblue] 10 | ValueObjectD[label="ValueObjectD\l", comment=VO, shape=box, color=lightblue] 11 | AggregateRootARepo[label="AggregateRootARepo", comment=Repo, shape=box, color=yellow] 12 | Router[label="Router", comment=Provider, shape=box, color=purple] 13 | AggregateRootA -> EntityB 14 | AggregateRootA -> ValueObjectC 15 | EntityB -> ValueObjectD 16 | AggregateRootB -> AggregateRootA[style="dashed"] 17 | AggregateRootARepo -> AggregateRootA[style="dashed"] 18 | } 19 | subgraph cluster2 { 20 | label=subdomain2 21 | AggregateRootC[label="AggregateRootA\l", comment=AR, shape=box] 22 | EntityC[label="EntityB\l", comment=E, shape=box] 23 | AggregateRootC -> EntityC 24 | } 25 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tequila: Compare DDD Model with code 2 | 3 | ## How to use? 4 | ### Prepare 5 | * install golang 6 | * set env variable: GOPATH 7 | * install graphviz(http://graphviz.org/) 8 | * install doxygen(http://www.stack.nl/~dimitri/doxygen/) 9 | 10 | ### Example: 11 | * DDD Model(![dot file](/examples/cargo-problem.dot)) 12 | 13 | ![](https://rawgit.com/newlee/tequila/master/examples/cargo-problem.png) 14 | 15 | * ![Code](/examples/bc-code) 16 | * ![Doxygen File](/examples/bc-code/Doxyfile) 17 | 18 | ### Features Done 19 | * DDD model validate 20 | * Cpp code check with DDD model 21 | * Inherit support 22 | * Include dependencies visualization 23 | 24 | ### Features TODO 25 | * Output detail result for cpp code check 26 | * Cpp code check with DDD solution domain 27 | 28 | ### Build & Run Cpp example: 29 | * generate doxygen dot files: 30 | `doxygen examples/step2-code/Doxyfile` 31 | * build & run example: 32 | `go build && ./tequila ` 33 | 34 | ### Build & Run Java example: 35 | * generate doxygen dot files: 36 | `doxygen examples/step2-Java/Doxyfile` 37 | * build & run example: 38 | `go build && ./tequila ` 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Li Xin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/bc-code/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set the minimum version of CMake that can be used 2 | # To find the cmake version run 3 | # $ cmake --version 4 | cmake_minimum_required(VERSION 3.0) 5 | 6 | # Set the project name 7 | project (bc_demo) 8 | 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 10 | 11 | # Create a sources variable with a link to all cpp files to compile 12 | file(GLOB_RECURSE SOURCES src/*.cpp) 13 | 14 | 15 | # Add an executable with the above sources 16 | add_executable(bc_demo ${SOURCES} main.cpp) 17 | 18 | # Set the direcoties that should be included in the build command for this target 19 | # when running g++ these will be included as -I/directory/path/ 20 | target_include_directories(bc_demo 21 | PRIVATE ${PROJECT_SOURCE_DIR}/include 22 | ) 23 | 24 | include_directories(${PROJECT_SOURCE_DIR}/include) 25 | 26 | enable_testing() 27 | find_package(GTest REQUIRED) 28 | include_directories(${GTEST_INCLUDE_DIRS}) 29 | 30 | file(GLOB TEST_SRC_FILES test/*.cpp) 31 | add_executable(bc_demo_test ${SOURCES} ${TEST_SRC_FILES}) 32 | target_link_libraries(bc_demo_test ${GTEST_LIBRARIES}) 33 | 34 | 35 | add_test(AllTests bc_demo_test) -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/call.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | //"fmt" 5 | 6 | . "github.com/newlee/tequila/viz" 7 | "github.com/spf13/cobra" 8 | //"strings" 9 | "strings" 10 | ) 11 | 12 | var callCmd *cobra.Command = &cobra.Command{ 13 | Use: "call", 14 | Short: "icall grpah", 15 | Long: ``, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | source := cmd.Flag("source").Value.String() 18 | filter := cmd.Flag("filter").Value.String() 19 | result := ParseICallGraph(source, filter) 20 | clusterResults := []string{ 21 | "com.lenovo.awakens.model.product.Configuration.", 22 | } 23 | var nodeFilter = func(key string) bool { 24 | //if strings.HasPrefix(key, "com.lenovo.awakens.model.product.Configuration.") { 25 | for _, cs := range clusterResults { 26 | if strings.Contains(key, cs) { 27 | return false 28 | } 29 | } 30 | //} 31 | 32 | return true 33 | } 34 | result.ToDot(cmd.Flag("output").Value.String(), ".", nodeFilter) 35 | result.ToDataSet("", ".", nodeFilter) 36 | }, 37 | } 38 | 39 | func init() { 40 | rootCmd.AddCommand(callCmd) 41 | 42 | callCmd.Flags().StringP("source", "s", "", "source code directory") 43 | callCmd.Flags().StringP("filter", "f", "coll__graph.dot", "dot file filter") 44 | callCmd.Flags().StringP("output", "o", "dep.dot", "output dot file name") 45 | } 46 | -------------------------------------------------------------------------------- /examples/bc-code/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "repositories/repository.h" 3 | #include "services/service.h" 4 | #include "gateways/gateway.h" 5 | #include "interface/api.h" 6 | #include "Hypodermic/ContainerBuilder.h" 7 | 8 | using namespace repositories; 9 | using namespace services; 10 | using namespace gateways; 11 | using namespace Hypodermic; 12 | 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | ContainerBuilder builder; 17 | builder.registerType< CargoRepository >().singleInstance(); 18 | builder.registerType< CargoProviderImpl >().singleInstance().as< CargoProvider >(); 19 | builder.registerType< CargoService >().singleInstance(); 20 | builder.registerType().singleInstance(); 21 | 22 | auto container = builder.build(); 23 | 24 | std::shared_ptr api = container->resolve(); 25 | std::shared_ptr cargoRepo = container->resolve(); 26 | api::CreateCargoMsg* msg = new api::CreateCargoMsg(); 27 | msg->Id = 1; 28 | msg->AfterDays = 10; 29 | api->CreateCargo(msg); 30 | std::cout<< "hello" << "\n"; 31 | std::cout<< cargoRepo->FindById(1)->getId()<<"\n"; 32 | return 0; 33 | } 34 | 35 | // Add this code snippet just for doxygen... 36 | namespace std { template class shared_ptr { T *dummy_for_doxygen; }; } 37 | -------------------------------------------------------------------------------- /viz/coll_viz.go: -------------------------------------------------------------------------------- 1 | package viz 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func ParseColl(codeDir string, filter string) *FullGraph { 8 | fullGraph = &FullGraph{ 9 | NodeList: make(map[string]string), 10 | RelationList: make(map[string]*Relation), 11 | } 12 | codeDotFiles := codeDotFiles(codeDir, func(path string) bool { 13 | return strings.HasSuffix(path, filter) 14 | }) 15 | 16 | for _, codeDotfile := range codeDotFiles { 17 | parseDotFile(codeDotfile) 18 | } 19 | 20 | return fullGraph 21 | } 22 | 23 | func ParseICallGraph(codeDir string, filter string) *FullGraph { 24 | fullGraph = &FullGraph{ 25 | NodeList: make(map[string]string), 26 | RelationList: make(map[string]*Relation), 27 | } 28 | codeDotFiles := codeDotFiles(codeDir, func(path string) bool { 29 | return strings.HasSuffix(path, "_icgraph.dot") && strings.Contains(path, filter) 30 | }) 31 | 32 | for _, codeDotfile := range codeDotFiles { 33 | parseDotFile(codeDotfile) 34 | } 35 | 36 | return fullGraph 37 | } 38 | 39 | func ParseICallGraphStart() { 40 | fullGraph = &FullGraph{ 41 | NodeList: make(map[string]string), 42 | RelationList: make(map[string]*Relation), 43 | } 44 | } 45 | 46 | func ParseICallGraphByBuffer(buf []byte) { 47 | parseFromBuffer(buf) 48 | } 49 | 50 | func ParseICallGraphEnd() *FullGraph { 51 | return fullGraph 52 | } 53 | -------------------------------------------------------------------------------- /examples/inheritance-tree-code/code.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Domain { 4 | class Entity 5 | { 6 | public: 7 | int Id; 8 | }; 9 | 10 | class AggregateRoot: public Entity 11 | { 12 | }; 13 | 14 | 15 | class ValueObject 16 | { 17 | }; 18 | 19 | class Provider 20 | { 21 | 22 | }; 23 | 24 | class Router: public Provider 25 | { 26 | public: 27 | virtual int Selete() = 0; 28 | }; 29 | 30 | static Router* router; 31 | 32 | class ValueObjectC: public ValueObject 33 | { 34 | public: 35 | ValueObjectC(){}; 36 | ~ValueObjectC(){}; 37 | 38 | }; 39 | 40 | class ValueObjectD: public ValueObjectC 41 | { 42 | public: 43 | ValueObjectD(){}; 44 | ~ValueObjectD(){}; 45 | 46 | }; 47 | 48 | class EntityB: public Entity 49 | { 50 | public: 51 | EntityB(){}; 52 | ~EntityB(){}; 53 | ValueObjectD* vo_d; 54 | void init(){ 55 | vo_d = new ValueObjectD(); 56 | std::cout << "entity b init" << "\n"; 57 | }; 58 | }; 59 | 60 | class EntityC: public EntityB 61 | { 62 | public: 63 | EntityC(){}; 64 | ~EntityC(){}; 65 | void init(){ 66 | 67 | }; 68 | }; 69 | 70 | class AggregateRootA: public AggregateRoot 71 | { 72 | public: 73 | AggregateRootA(){}; 74 | ~AggregateRootA(){}; 75 | EntityB* entity_b; 76 | EntityC* entity_c; 77 | ValueObjectC* vo_c; 78 | void Init(){ 79 | entity_b = new EntityB(); 80 | entity_b->init(); 81 | router->Selete(); 82 | entity_c = new EntityC(); 83 | entity_c->init(); 84 | }; 85 | }; 86 | 87 | class AggregateRootB: public AggregateRootA 88 | { 89 | public: 90 | AggregateRootB(){}; 91 | ~AggregateRootB(){}; 92 | }; 93 | 94 | } -------------------------------------------------------------------------------- /model/common.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Entity struct { 4 | name string 5 | Entities []*Entity 6 | VOs []*ValueObject 7 | Refs []*Entity 8 | } 9 | 10 | type ValueObject struct { 11 | name string 12 | } 13 | 14 | type Repository struct { 15 | name string 16 | For string 17 | } 18 | type Provider struct { 19 | name string 20 | } 21 | 22 | func NewEntity(name string) *Entity { 23 | return &Entity{name: name} 24 | } 25 | 26 | func NewValueObject(name string) *ValueObject { 27 | return &ValueObject{name: name} 28 | } 29 | 30 | func NewRepository(name string) *Repository { 31 | return &Repository{name: name} 32 | } 33 | 34 | func NewProvider(name string) *Provider { 35 | return &Provider{name: name} 36 | } 37 | 38 | func (entity *Entity) AppendVO(vo *ValueObject) { 39 | for _, item := range entity.VOs { 40 | if item.name == vo.name { 41 | return 42 | } 43 | } 44 | entity.VOs = append(entity.VOs, vo) 45 | } 46 | func (entity *Entity) Compare(other *Entity) bool { 47 | if len(entity.Entities) != len(other.Entities) { 48 | return false 49 | } 50 | if len(entity.VOs) != len(other.VOs) { 51 | return false 52 | } 53 | em := make(map[string]*Entity) 54 | for _, childEntity := range entity.Entities { 55 | em[childEntity.name] = childEntity 56 | } 57 | for _, childEntity := range other.Entities { 58 | if !em[childEntity.name].Compare(childEntity) { 59 | return false 60 | } 61 | } 62 | vom := make(map[string]*ValueObject) 63 | for _, vo := range entity.VOs { 64 | vom[vo.name] = vo 65 | } 66 | for _, vo := range other.VOs { 67 | if _, ok := vom[vo.name]; !ok { 68 | return false 69 | } 70 | } 71 | return true 72 | } 73 | 74 | func (repo *Repository) Compare(other *Repository) bool { 75 | return repo.For == other.For 76 | } 77 | -------------------------------------------------------------------------------- /examples/cargo-bc.dot: -------------------------------------------------------------------------------- 1 | digraph g { 2 | node[style = filled, color = orange]; 3 | 4 | subgraph cluster1 { 5 | label=api 6 | style=filled 7 | color="#45AE6A" 8 | penwidth=4 9 | fillcolor="#91EEC8" 10 | node [style=filled,color=white,shape=box]; 11 | Api[label="Api", comment=Api] 12 | 13 | } 14 | 15 | subgraph cluster2 { 16 | label=services 17 | style=filled 18 | color="#3DA2AB" 19 | fillcolor="#85EBF0" 20 | penwidth=4 21 | node [style=filled,color=white,shape=box]; 22 | CargoService[label="CargoService", comment=Service, shape=box, color=yellow] 23 | CargoProvider[label="CargoProvider", comment=Provider, shape=box, color=yellow] 24 | CargoService -> CargoProvider[style="dashed"] 25 | } 26 | 27 | subgraph cluster3 { 28 | label=domain 29 | style=filled 30 | color="#3DA2AB" 31 | fillcolor="#85EBF0" 32 | penwidth=4 33 | Cargo[label="Cargo\l", comment=AR, shape=box] 34 | Delivery[label="Delivery\l", comment=VO, shape=box, color=lightblue] 35 | Product[label="Product\l", comment=VO, shape=box, color=lightblue] 36 | Cargo -> Delivery 37 | Cargo -> Product 38 | 39 | } 40 | subgraph cluster4 { 41 | label=repositories 42 | style=filled 43 | color="#3DA2AB" 44 | fillcolor="#85EBF0" 45 | penwidth=4 46 | node [style=filled,color=white,shape=box]; 47 | CargoRepository[label="CargoRepository", comment=Repo, shape=box, color=yellow] 48 | } 49 | 50 | subgraph cluster5 { 51 | label=gateways 52 | style=filled 53 | color="#85467E" 54 | fillcolor="#D084C4" 55 | penwidth=4 56 | node [style=filled,color=white,shape=box]; 57 | CargoProviderImpl[label="CargoProviderImpl", comment=Provider, shape=box, color=purple] 58 | } 59 | Api -> CargoService[style="dashed"] 60 | CargoService -> CargoRepository[style="dashed"] 61 | CargoRepository -> Cargo[style="dashed"] 62 | CargoProviderImpl -> CargoProvider 63 | } -------------------------------------------------------------------------------- /viz/package.go: -------------------------------------------------------------------------------- 1 | package viz 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | var Level = 7 10 | 11 | type Package struct { 12 | Name string 13 | Imports map[string]string 14 | } 15 | 16 | type AllPackage struct { 17 | Packages map[string]*Package 18 | } 19 | 20 | func NewAllPackage() *AllPackage { 21 | return &AllPackage{Packages: make(map[string]*Package)} 22 | } 23 | 24 | func (all *AllPackage) Add(name string) *Package { 25 | tmp := strings.Split(name, ".") 26 | levelName := name 27 | if len(tmp) > Level { 28 | levelName = strings.Join(tmp[:(Level)], ".") 29 | } 30 | 31 | if _, ok := all.Packages[levelName]; !ok { 32 | all.Packages[levelName] = &Package{Name: levelName, Imports: make(map[string]string)} 33 | } 34 | return all.Packages[levelName] 35 | } 36 | 37 | func (p *Package) AddImport(importPkg string) { 38 | tmp := strings.Split(importPkg, ".") 39 | levelName := importPkg 40 | if len(tmp) > Level { 41 | levelName = strings.Join(tmp[:(Level)], ".") 42 | } 43 | 44 | if _, ok := p.Imports[levelName]; !ok { 45 | p.Imports[levelName] = "" 46 | } 47 | } 48 | 49 | func (all *AllPackage) Print() { 50 | pkgs := make([]*Package, 0) 51 | for key := range all.Packages { 52 | pkgs = append(pkgs, all.Packages[key]) 53 | } 54 | sort.Slice(pkgs, func(i, j int) bool { 55 | return strings.Compare(pkgs[i].Name, pkgs[j].Name) < 0 56 | }) 57 | 58 | for _, pkg := range pkgs { 59 | pkg.Print() 60 | } 61 | } 62 | 63 | func (p *Package) Print() { 64 | if len(p.Imports) > 0 { 65 | fmt.Printf("%s\n", p.Name) 66 | return 67 | } 68 | //pkgs := make([]string,0) 69 | //for key := range p.Imports { 70 | // pkgs= append(pkgs, key) 71 | //} 72 | //sort.Slice(pkgs, func(i, j int) bool { 73 | // return strings.Compare(pkgs[i], pkgs[j] ) < 0 74 | //}) 75 | // 76 | //fmt.Printf("%s :\n", p.Name) 77 | //for _, importPkg := range pkgs { 78 | // fmt.Printf(" %s\n",importPkg) 79 | //} 80 | } 81 | -------------------------------------------------------------------------------- /examples/step2-code/code.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Domain { 4 | class Entity 5 | { 6 | public: 7 | int Id; 8 | }; 9 | 10 | class AggregateRoot: public Entity 11 | { 12 | }; 13 | 14 | 15 | class ValueObject 16 | { 17 | }; 18 | 19 | class Provider 20 | { 21 | 22 | }; 23 | 24 | class Router: public Provider 25 | { 26 | public: 27 | virtual int Selete() = 0; 28 | }; 29 | 30 | static Router* router; 31 | 32 | class ValueObjectC: public ValueObject 33 | { 34 | public: 35 | ValueObjectC(){}; 36 | ~ValueObjectC(){}; 37 | 38 | }; 39 | 40 | class ValueObjectD: public ValueObject 41 | { 42 | public: 43 | ValueObjectD(){}; 44 | ~ValueObjectD(){}; 45 | 46 | }; 47 | 48 | class EntityB: public Entity 49 | { 50 | public: 51 | EntityB(){}; 52 | ~EntityB(){}; 53 | ValueObjectD* vo_d; 54 | 55 | }; 56 | 57 | class AggregateRootA: public AggregateRoot 58 | { 59 | public: 60 | AggregateRootA(){}; 61 | ~AggregateRootA(){}; 62 | EntityB* entity_b; 63 | ValueObjectC* vo_c; 64 | void Init(){ 65 | router->Selete(); 66 | }; 67 | }; 68 | 69 | class AggregateRootB: public AggregateRoot 70 | { 71 | public: 72 | AggregateRootB(){}; 73 | ~AggregateRootB(){}; 74 | AggregateRootA* a; 75 | }; 76 | 77 | } 78 | 79 | namespace Repositories { 80 | using namespace Domain; 81 | 82 | class Repository{ 83 | }; 84 | 85 | class AggregateRootARepo: public Repository 86 | { 87 | public: 88 | AggregateRootARepo(){}; 89 | ~AggregateRootARepo(){}; 90 | void Save(AggregateRootA *a){ 91 | a->Init(); 92 | std::cout << "saved" << "\n"; 93 | }; 94 | private: 95 | std::vector ar_list; 96 | }; 97 | 98 | } 99 | 100 | namespace Gateways { 101 | using namespace Domain; 102 | 103 | class FakeRouter: public Router 104 | { 105 | public: 106 | FakeRouter(){}; 107 | ~FakeRouter(){}; 108 | int Selete(){ 109 | std::cout << "routed" << "\n"; 110 | return 1; 111 | } 112 | }; 113 | 114 | } -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/java_code.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "github.com/newlee/tequila/viz" 6 | "github.com/spf13/cobra" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | var javaCodeCmd *cobra.Command = &cobra.Command{ 13 | Use: "jc", 14 | Short: "java code package dependencies", 15 | Long: ``, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | source := cmd.Flag("source").Value.String() 18 | call := cmd.Flag("call").Value.String() 19 | codeFiles := make([]string, 0) 20 | filepath.Walk(source, func(path string, fi os.FileInfo, err error) error { 21 | if strings.HasSuffix(path, ".java") { 22 | codeFiles = append(codeFiles, path) 23 | } 24 | 25 | return nil 26 | }) 27 | allPackage := viz.NewAllPackage() 28 | for _, codeFileName := range codeFiles { 29 | codeFile, _ := os.Open(codeFileName) 30 | scanner := bufio.NewScanner(codeFile) 31 | scanner.Split(bufio.ScanLines) 32 | var pkg *viz.Package 33 | for scanner.Scan() { 34 | line := scanner.Text() 35 | 36 | if strings.HasPrefix(line, "package") && !strings.Contains(line, call) { 37 | tmp := strings.FieldsFunc(line, func(r rune) bool { 38 | return r == ' ' || r == ';' 39 | }) 40 | //fmt.Println(tmp[1]) 41 | pkg = allPackage.Add(tmp[1]) 42 | } 43 | 44 | if pkg != nil && strings.HasPrefix(line, "import") && strings.Contains(line, call) { 45 | tmp := strings.FieldsFunc(line, func(r rune) bool { 46 | return r == ' ' || r == ';' 47 | }) 48 | importPkg := tmp[1] 49 | 50 | if strings.HasPrefix(importPkg, "com") { 51 | pkg.AddImport(importPkg) 52 | } 53 | 54 | } 55 | 56 | if strings.HasPrefix(line, "public") { 57 | break 58 | } 59 | } 60 | 61 | codeFile.Close() 62 | } 63 | allPackage.Print() 64 | }, 65 | } 66 | 67 | func init() { 68 | rootCmd.AddCommand(javaCodeCmd) 69 | 70 | javaCodeCmd.Flags().StringP("source", "s", "", "source code directory") 71 | javaCodeCmd.Flags().StringP("call", "c", "", "filter by call") 72 | javaCodeCmd.Flags().StringP("output", "o", "dep.dot", "output dot file name") 73 | } 74 | -------------------------------------------------------------------------------- /viz/filter_test.go: -------------------------------------------------------------------------------- 1 | package viz_test 2 | 3 | import ( 4 | . "github.com/newlee/tequila/viz" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Viz", func() { 11 | Context("Filter by regexp", func() { 12 | It("filter with white list", func() { 13 | filter := NewRegexpFilter() 14 | filter.AddReg(".*Hello.*") 15 | filter.AddReg("^World.*") 16 | filter.AddReg("^FOO$") 17 | 18 | matchedString := []string{"Hi Hello", "Hi Hello w", "World H", "World", "FOO"} 19 | for _, s := range matchedString { 20 | Expect(filter.Match(s)).Should(BeTrue()) 21 | } 22 | 23 | unMatchedString := []string{"Hi Hell", "Hi Hell w", "Hi World", "FOO B", "B FOO", "BAR"} 24 | for _, s := range unMatchedString { 25 | Expect(filter.Match(s)).Should(BeFalse()) 26 | } 27 | }) 28 | 29 | It("filter with black list", func() { 30 | filter := NewRegexpFilter() 31 | filter.AddReg(".*") 32 | filter.AddReg("- ^Hello$") 33 | filter.AddReg("- ^((?!World).)*FOO((?!World).)*$") 34 | 35 | unMatchedString := []string{"Hi Hello", "Hello w", "World H", "World FOO", "FOO World"} 36 | for _, s := range unMatchedString { 37 | Expect(filter.Match(s)).Should(BeTrue()) 38 | } 39 | 40 | matchedString := []string{"Hello", "W FOO Q"} 41 | for _, s := range matchedString { 42 | Expect(filter.Match(s)).Should(BeFalse()) 43 | } 44 | }) 45 | 46 | It("filter with excludes", func() { 47 | filter := NewRegexpFilter() 48 | filter.AddReg(".*") 49 | filter.AddExclude("Hello") 50 | 51 | unMatchedString := []string{"Hi Hello", "Hello w", "World H", "World FOO", "FOO World"} 52 | for _, s := range unMatchedString { 53 | Expect(filter.Match(s)).Should(BeTrue()) 54 | } 55 | 56 | matchedString := []string{"Hello"} 57 | for _, s := range matchedString { 58 | Expect(filter.Match(s)).Should(BeFalse()) 59 | } 60 | 61 | for _, s := range matchedString { 62 | Expect(filter.UnMatch(s)).Should(BeFalse()) 63 | } 64 | for _, s := range matchedString { 65 | Expect(filter.NotMatch(s)).Should(BeFalse()) 66 | } 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /examples/bc-code/test/app_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/interface/api.h" 3 | #include "../include/services/service.h" 4 | #include "../include/repositories/repository.h" 5 | #include "Hypodermic/ContainerBuilder.h" 6 | 7 | using namespace Hypodermic; 8 | using namespace domain; 9 | using namespace repositories; 10 | using namespace services; 11 | 12 | struct StubCargoProvider : services::CargoProvider{ 13 | int cargo_id; 14 | int after_days; 15 | virtual void Confirm(Cargo *cargo) override; 16 | }; 17 | 18 | static const int ID = 1; 19 | static const int AFTER_DAYS = 10; 20 | 21 | //StubCargoProvider* provider = new StubCargoProvider(); 22 | auto provider = std::make_shared< StubCargoProvider >(); 23 | 24 | api::Api* createApi() { 25 | ContainerBuilder builder; 26 | builder.registerType< CargoRepository >().singleInstance(); 27 | builder.registerInstance(provider).as(); 28 | builder.registerType< CargoService >().singleInstance(); 29 | builder.registerType().singleInstance(); 30 | 31 | auto container = builder.build(); 32 | 33 | std::shared_ptr api = container->resolve(); 34 | 35 | return api.get(); 36 | } 37 | 38 | void createCargo(api::CreateCargoMsg* msg) { 39 | api::Api* api = createApi(); 40 | api->CreateCargo(msg); 41 | } 42 | 43 | TEST(bc_demo_test, create_cargo) 44 | { 45 | api::CreateCargoMsg* msg = new api::CreateCargoMsg(); 46 | msg->Id = ID; 47 | msg->AfterDays = AFTER_DAYS; 48 | createCargo(msg); 49 | EXPECT_EQ(msg->Id, provider->cargo_id); 50 | EXPECT_EQ(msg->AfterDays, provider->after_days); 51 | } 52 | 53 | TEST(bc_demo_test, delay_cargo) 54 | { 55 | api::Api* api = createApi(); 56 | api::CreateCargoMsg* msg = new api::CreateCargoMsg(); 57 | msg->Id = ID; 58 | msg->AfterDays = AFTER_DAYS; 59 | api->CreateCargo(msg); 60 | api->Delay(ID,2); 61 | EXPECT_EQ(ID, provider->cargo_id); 62 | EXPECT_EQ(12, provider->after_days); 63 | } 64 | 65 | 66 | void StubCargoProvider::Confirm(Cargo *cargo) { 67 | this->cargo_id = cargo->getId(); 68 | this->after_days = cargo->afterDays(); 69 | } 70 | -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | homedir "github.com/mitchellh/go-homedir" 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | var cfgFile string 13 | 14 | // rootCmd represents the base command when called without any subcommands 15 | var rootCmd = &cobra.Command{ 16 | Use: "tq_viz_cli", 17 | Short: "tequila viz command line ", 18 | Long: ``, 19 | // Uncomment the following line if your bare application 20 | // has an action associated with it: 21 | // Run: func(cmd *cobra.Command, args []string) { }, 22 | } 23 | 24 | // Execute adds all child commands to the root command and sets flags appropriately. 25 | // This is called by main.main(). It only needs to happen once to the rootCmd. 26 | func Execute() { 27 | if err := rootCmd.Execute(); err != nil { 28 | fmt.Println(err) 29 | os.Exit(1) 30 | } 31 | } 32 | 33 | func init() { 34 | cobra.OnInitialize(initConfig) 35 | // Here you will define your flags and configuration settings. 36 | // Cobra supports persistent flags, which, if defined here, 37 | // will be global for your application. 38 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tq_viz_cli.yaml)") 39 | 40 | // Cobra also supports local flags, which will only run 41 | // when this action is called directly. 42 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 43 | } 44 | 45 | // initConfig reads in config file and ENV variables if set. 46 | func initConfig() { 47 | if cfgFile != "" { 48 | // Use config file from the flag. 49 | viper.SetConfigFile(cfgFile) 50 | } else { 51 | // Find home directory. 52 | home, err := homedir.Dir() 53 | if err != nil { 54 | fmt.Println(err) 55 | os.Exit(1) 56 | } 57 | 58 | // Search config in home directory with name ".tq_viz_cli" (without extension). 59 | viper.AddConfigPath(home) 60 | viper.SetConfigName(".tq_viz_cli") 61 | } 62 | 63 | viper.AutomaticEnv() // read in environment variables that match 64 | 65 | // If a config file is found, read it in. 66 | if err := viper.ReadInConfig(); err == nil { 67 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/step3-code/code.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Domain { 4 | class Entity 5 | { 6 | public: 7 | int Id; 8 | }; 9 | 10 | class AggregateRoot: public Entity 11 | { 12 | }; 13 | 14 | 15 | class ValueObject 16 | { 17 | }; 18 | 19 | class Provider 20 | { 21 | 22 | }; 23 | 24 | class Router: public Provider 25 | { 26 | public: 27 | virtual int Selete() = 0; 28 | }; 29 | 30 | static Router* router; 31 | 32 | class ValueObjectC: public ValueObject 33 | { 34 | public: 35 | ValueObjectC(){}; 36 | ~ValueObjectC(){}; 37 | 38 | }; 39 | 40 | class ValueObjectD: public ValueObject 41 | { 42 | public: 43 | ValueObjectD(){}; 44 | ~ValueObjectD(){}; 45 | 46 | }; 47 | 48 | class EntityB: public Entity 49 | { 50 | public: 51 | EntityB(){}; 52 | ~EntityB(){}; 53 | ValueObjectD* vo_d; 54 | void init(){ 55 | vo_d = new ValueObjectD(); 56 | std::cout << "entity b init" << "\n"; 57 | }; 58 | }; 59 | 60 | class AggregateRootA: public AggregateRoot 61 | { 62 | public: 63 | AggregateRootA(){}; 64 | ~AggregateRootA(){}; 65 | EntityB* entity_b; 66 | ValueObjectC* vo_c; 67 | void Init(){ 68 | entity_b = new EntityB(); 69 | entity_b->init(); 70 | router->Selete(); 71 | }; 72 | }; 73 | 74 | class AggregateRootB: public AggregateRoot 75 | { 76 | public: 77 | AggregateRootB(){}; 78 | ~AggregateRootB(){}; 79 | EntityB* entity_b; 80 | AggregateRootA* a; 81 | void Init(){ 82 | a = new AggregateRootA(); 83 | a->Init(); 84 | a->entity_b->init(); 85 | }; 86 | }; 87 | 88 | } 89 | 90 | namespace Repositories { 91 | using namespace Domain; 92 | 93 | class Repository{ 94 | }; 95 | 96 | class AggregateRootARepo: public Repository 97 | { 98 | public: 99 | AggregateRootARepo(){}; 100 | ~AggregateRootARepo(){}; 101 | void Save(AggregateRootA *a){ 102 | a->Init(); 103 | std::cout << "saved" << "\n"; 104 | }; 105 | }; 106 | 107 | } 108 | 109 | namespace Gateways { 110 | using namespace Domain; 111 | 112 | class FakeRouter: public Router 113 | { 114 | public: 115 | FakeRouter(){}; 116 | ~FakeRouter(){}; 117 | int Selete(){ 118 | std::cout << "routed" << "\n"; 119 | return 1; 120 | } 121 | }; 122 | 123 | } -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/include.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | . "github.com/newlee/tequila/viz" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var includeCmd = &cobra.Command{ 10 | Use: "include", 11 | Short: "include dependencies of source code", 12 | Long: ``, 13 | Run: func(cmd *cobra.Command, args []string) { 14 | result := ParseInclude(cmd.Flag("source").Value.String()) 15 | 16 | if cmd.Flag("findCrossRefs").Value.String() == "true" { 17 | crossRefs := result.FindCrossRef(MergeHeaderFunc) 18 | for _, cf := range crossRefs { 19 | fmt.Println(cf) 20 | } 21 | return 22 | } 23 | 24 | if cmd.Flag("entryPoints").Value.String() == "true" { 25 | entryPoints := result.EntryPoints(MergeHeaderFunc) 26 | for _, cf := range entryPoints { 27 | fmt.Println(cf) 28 | } 29 | return 30 | } 31 | 32 | if cmd.Flag("fanInFanOut").Value.String() == "true" { 33 | fans := result.SortedByFan(MergeHeaderFunc) 34 | fmt.Println("Name\tTotal\tFan-In\tFan-Out") 35 | for _, fan := range fans { 36 | fmt.Printf("%s\t%v\t%v\t%v\n", fan.Name, fan.FanIn+fan.FanOut, fan.FanIn, fan.FanOut) 37 | } 38 | return 39 | } 40 | 41 | if cmd.Flag("mergeHeader").Value.String() == "true" { 42 | result = result.MergeHeaderFile(MergeHeaderFunc) 43 | } 44 | 45 | if cmd.Flag("mergePackage").Value.String() == "true" { 46 | result = result.MergeHeaderFile(MergePackageFunc) 47 | } 48 | var filter = func(key string) bool { 49 | return key == "main.cpp" || key == "main" 50 | } 51 | result.ToDot(cmd.Flag("output").Value.String(), "/", filter) 52 | }, 53 | } 54 | 55 | func init() { 56 | rootCmd.AddCommand(includeCmd) 57 | 58 | includeCmd.Flags().StringP("source", "s", "", "source code directory") 59 | includeCmd.Flags().StringP("output", "o", "dep.dot", "output dot file name") 60 | includeCmd.Flags().BoolP("findCrossRefs", "C", false, "find cross references") 61 | includeCmd.Flags().BoolP("fanInFanOut", "F", false, "sorted fan-in and fan-out") 62 | includeCmd.Flags().BoolP("entryPoints", "E", false, "list entry points") 63 | includeCmd.Flags().BoolP("mergeHeader", "H", false, "merge header file to same cpp file") 64 | includeCmd.Flags().BoolP("mergePackage", "P", false, "merge package/folder for include dependencies") 65 | } 66 | -------------------------------------------------------------------------------- /dot.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/awalterschulze/gographviz" 5 | . "github.com/newlee/tequila/model" 6 | "io/ioutil" 7 | //"fmt" 8 | ) 9 | 10 | func edgesKey(edges map[string][]*gographviz.Edge) []string { 11 | result := make([]string, 0) 12 | for key := range edges { 13 | result = append(result, key) 14 | } 15 | return result 16 | } 17 | 18 | func ParseProblemModel(dotFile string) *ProblemModel { 19 | fbuf, _ := ioutil.ReadFile(dotFile) 20 | g, _ := gographviz.Read(fbuf) 21 | 22 | c2pMap := make(map[string]string) 23 | p2c := g.Relations.ParentToChildren 24 | 25 | subDomains := make(map[string]*SubDomain) 26 | 27 | if _, ok := p2c["g"]; ok { 28 | for key := range p2c["g"] { 29 | c2pMap[key] = "subdomain" 30 | } 31 | subDomains["subdomain"] = NewSubDomain() 32 | } else { 33 | for clusterKey := range p2c { 34 | subDomainName := g.SubGraphs.SubGraphs[clusterKey].Attrs["label"] 35 | for key := range p2c[clusterKey] { 36 | c2pMap[key] = subDomainName 37 | } 38 | subDomains[subDomainName] = NewSubDomain() 39 | } 40 | } 41 | cms := InitCommentMapping() 42 | for _, node := range g.Nodes.Nodes { 43 | subDomain := subDomains[c2pMap[node.Name]] 44 | subDomain.AddNode(cms, node.Name, node.Attrs["comment"]) 45 | } 46 | 47 | for key := range g.Edges.SrcToDsts { 48 | edgeKeys := edgesKey(g.Edges.SrcToDsts[key]) 49 | subDomain := subDomains[c2pMap[key]] 50 | subDomain.AddRelations(key, edgeKeys) 51 | } 52 | 53 | return &ProblemModel{SubDomains: subDomains} 54 | } 55 | 56 | func ParseSolutionModel(dotFile string) *BCModel { 57 | fbuf, _ := ioutil.ReadFile(dotFile) 58 | g, _ := gographviz.Read(fbuf) 59 | 60 | p2c := g.Relations.ParentToChildren 61 | 62 | model := NewBCModel() 63 | for clusterKey := range p2c { 64 | if clusterKey != "g" { 65 | layerName := g.SubGraphs.SubGraphs[clusterKey].Attrs["label"] 66 | model.AppendLayer(layerName) 67 | for key := range p2c[clusterKey] { 68 | model.AppendNode(layerName, key) 69 | } 70 | } 71 | } 72 | for _, node := range g.Nodes.Nodes { 73 | model.AddNode(node.Name, node.Attrs["comment"]) 74 | } 75 | 76 | for key := range g.Edges.SrcToDsts { 77 | edgeKeys := edgesKey(g.Edges.SrcToDsts[key]) 78 | model.AddRelations(key, edgeKeys) 79 | } 80 | 81 | return model 82 | } 83 | -------------------------------------------------------------------------------- /viz/viz_test.go: -------------------------------------------------------------------------------- 1 | package viz_test 2 | 3 | import ( 4 | . "github.com/newlee/tequila/viz" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | "strings" 9 | ) 10 | 11 | var _ = Describe("Viz", func() { 12 | Context("Parse all include dependencies", func() { 13 | It("bc code", func() { 14 | codeDir := "../examples/bc-code/html" 15 | result := ParseInclude(codeDir) 16 | Expect(len(result.NodeList)).Should(Equal(12)) 17 | Expect(len(result.RelationList)).Should(Equal(14)) 18 | var mergeFunc = func(input string) string { 19 | return strings.Replace(strings.Replace(input, ".h", "", -1), ".cpp", "", -1) 20 | } 21 | crossRefs := result.FindCrossRef(mergeFunc) 22 | Expect(len(crossRefs)).Should(Equal(0), "Cross references: %v", crossRefs) 23 | }) 24 | 25 | It("merge header files", func() { 26 | codeDir := "../examples/bc-code/html" 27 | fullGraph := ParseInclude(codeDir) 28 | 29 | result := fullGraph.MergeHeaderFile(MergeHeaderFunc) 30 | Expect(len(result.NodeList)).Should(Equal(8)) 31 | Expect(len(result.RelationList)).Should(Equal(10)) 32 | }) 33 | 34 | It("merge package", func() { 35 | codeDir := "../examples/bc-code/html" 36 | fullGraph := ParseInclude(codeDir) 37 | 38 | result := fullGraph.MergeHeaderFile(MergePackageFunc) 39 | Expect(len(result.NodeList)).Should(Equal(6)) 40 | Expect(len(result.RelationList)).Should(Equal(8)) 41 | }) 42 | 43 | It("entry points", func() { 44 | codeDir := "../examples/bc-code/html" 45 | fullGraph := ParseInclude(codeDir) 46 | 47 | entryPoints := fullGraph.EntryPoints(MergePackageFunc) 48 | Expect(len(entryPoints)).Should(Equal(2)) 49 | }) 50 | 51 | It("sort by fan-in fan-out", func() { 52 | codeDir := "../examples/bc-code/html" 53 | fullGraph := ParseInclude(codeDir) 54 | 55 | fans := fullGraph.SortedByFan(MergeHeaderFunc) 56 | Expect(len(fans)).Should(Equal(8)) 57 | //fan := fans[0] 58 | //Expect(fan.Name).Should(Equal("services/service")) 59 | //Expect(fan.FanIn).Should(Equal(3)) 60 | //Expect(fan.FanOut).Should(Equal(1)) 61 | }) 62 | }) 63 | Context("Parse all collaboration", func() { 64 | It("bc code", func() { 65 | codeDir := "../examples/step2-Java/html" 66 | result := ParseColl(codeDir, "coll__graph.dot") 67 | Expect(len(result.NodeList)).Should(Equal(13)) 68 | Expect(len(result.RelationList)).Should(Equal(15)) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/utils.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "github.com/newlee/tequila/viz" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func doFiles(fileNames []string, fileCallback func(), callback func(string, string)) { 11 | for _, fileName := range fileNames { 12 | file, _ := os.Open(fileName) 13 | scanner := bufio.NewScanner(file) 14 | scanner.Split(bufio.ScanLines) 15 | fileCallback() 16 | for scanner.Scan() { 17 | line := scanner.Text() 18 | callback(line, fileName) 19 | } 20 | 21 | file.Close() 22 | } 23 | } 24 | 25 | var emptyFilter = func(line string) bool { 26 | return true 27 | } 28 | 29 | var pkgSplit = func(r rune) bool { 30 | return r == ' ' || r == '(' || r == ',' || r == '\'' || r == '"' || r == ')' 31 | } 32 | var tableSplit = func(r rune) bool { 33 | return r == ' ' || r == ',' || r == '.' || r == '"' || r == ':' || r == '(' || r == ')' || r == ')' || r == '%' || r == '!' || r == '\'' 34 | } 35 | 36 | func isComment(first string) bool { 37 | return strings.HasPrefix(first, "/*") || strings.HasPrefix(first, "*") || strings.HasPrefix(first, "//") 38 | } 39 | 40 | func doSplit(line string, f func(rune) bool, callback func(string)) { 41 | tmp := strings.FieldsFunc(line, f) 42 | for _, s := range tmp { 43 | callback(s) 44 | } 45 | } 46 | 47 | func doPkg(s string, pkgFilter func(line string) bool, callback func(string, string)) { 48 | pkg := "" 49 | sp := "" 50 | if strings.HasPrefix(s, "PKG_") && pkgFilter(strings.Split(s, ".")[0]) { 51 | s = strings.Replace(s, "\"", "", -1) 52 | tmp := strings.Split(s, ".") 53 | pkg = tmp[0] 54 | if len(tmp) > 1 && strings.HasPrefix(tmp[1], "P_") { 55 | sp = tmp[1] 56 | callback(pkg, sp) 57 | } 58 | } 59 | } 60 | 61 | func doPkgLine(line string, pkgFilter func(line string) bool, callback func(string, string)) { 62 | doSplit(line, pkgSplit, func(s string) { 63 | doPkg(s, pkgFilter, callback) 64 | }) 65 | } 66 | 67 | func doTableLine(line string, tableFilter func(line string) bool, callback func(string)) { 68 | doSplit(line, tableSplit, func(s string) { 69 | if strings.HasPrefix(s, "T_") && tableFilter(s) && !viz.IsChineseChar(s) { 70 | callback(s) 71 | } 72 | }) 73 | } 74 | 75 | func doCreatePkg(line string, callback func(string)) { 76 | tmp := strings.FieldsFunc(line, pkgSplit) 77 | for _, key := range tmp { 78 | if strings.HasPrefix(key, "PKG_") { 79 | callback(key) 80 | } 81 | } 82 | } 83 | func doCreateProcedure(line string, callback func(string)) { 84 | tmp := strings.FieldsFunc(line, pkgSplit) 85 | for _, key := range tmp { 86 | if strings.HasPrefix(key, "P_") { 87 | callback(key) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /examples/subdomain-code/code.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace subdomain1{ 4 | namespace Domain { 5 | class Entity 6 | { 7 | public: 8 | int Id; 9 | }; 10 | 11 | class AggregateRoot: public Entity 12 | { 13 | }; 14 | 15 | 16 | class ValueObject 17 | { 18 | }; 19 | 20 | class Provider 21 | { 22 | 23 | }; 24 | 25 | class Router: public Provider 26 | { 27 | public: 28 | virtual int Selete() = 0; 29 | }; 30 | 31 | static Router* router; 32 | 33 | class ValueObjectC: public ValueObject 34 | { 35 | public: 36 | ValueObjectC(){}; 37 | ~ValueObjectC(){}; 38 | 39 | }; 40 | 41 | class ValueObjectD: public ValueObject 42 | { 43 | public: 44 | ValueObjectD(){}; 45 | ~ValueObjectD(){}; 46 | 47 | }; 48 | 49 | class EntityB: public Entity 50 | { 51 | public: 52 | EntityB(){}; 53 | ~EntityB(){}; 54 | ValueObjectD* vo_d; 55 | void init(){ 56 | vo_d = new ValueObjectD(); 57 | std::cout << "entity b init" << "\n"; 58 | }; 59 | }; 60 | 61 | class AggregateRootA: public AggregateRoot 62 | { 63 | public: 64 | AggregateRootA(){}; 65 | ~AggregateRootA(){}; 66 | EntityB* entity_b; 67 | ValueObjectC* vo_c; 68 | void Init(){ 69 | entity_b = new EntityB(); 70 | entity_b->init(); 71 | router->Selete(); 72 | }; 73 | }; 74 | 75 | class AggregateRootB: public AggregateRoot 76 | { 77 | public: 78 | AggregateRootB(){}; 79 | ~AggregateRootB(){}; 80 | AggregateRootA* a; 81 | }; 82 | 83 | 84 | } 85 | 86 | namespace Repositories { 87 | using namespace Domain; 88 | 89 | class Repository{ 90 | }; 91 | 92 | class AggregateRootARepo: public Repository 93 | { 94 | public: 95 | AggregateRootARepo(){}; 96 | ~AggregateRootARepo(){}; 97 | void Save(AggregateRootA *a){ 98 | a->Init(); 99 | std::cout << "saved" << "\n"; 100 | }; 101 | }; 102 | 103 | } 104 | 105 | namespace Gateways { 106 | using namespace Domain; 107 | 108 | class FakeRouter: public Router 109 | { 110 | public: 111 | FakeRouter(){}; 112 | ~FakeRouter(){}; 113 | int Selete(){ 114 | std::cout << "routed" << "\n"; 115 | return 1; 116 | } 117 | }; 118 | 119 | } 120 | } 121 | 122 | namespace subdomain2 { 123 | namespace Domain { 124 | class Entity 125 | { 126 | public: 127 | int Id; 128 | }; 129 | 130 | class AggregateRoot: public Entity 131 | { 132 | }; 133 | 134 | 135 | class ValueObject 136 | { 137 | }; 138 | class EntityC: public Entity 139 | { 140 | public: 141 | EntityC(){}; 142 | ~EntityC(){}; 143 | }; 144 | 145 | class AggregateRootC: public AggregateRoot 146 | { 147 | public: 148 | AggregateRootC(){}; 149 | ~AggregateRootC(){}; 150 | EntityC* entity_c; 151 | }; 152 | } 153 | } -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/db_dep.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/newlee/tequila/viz" 6 | "github.com/spf13/cobra" 7 | "os" 8 | "path/filepath" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | var dbDepCmd *cobra.Command = &cobra.Command{ 14 | Use: "dd", 15 | Short: "database dependencies", 16 | Long: ``, 17 | Run: func(cmd *cobra.Command, args []string) { 18 | //defer profile.Start().Stop() 19 | source := cmd.Flag("source").Value.String() 20 | pkgFilterFile := cmd.Flag("package").Value.String() 21 | tableFilterFile := cmd.Flag("table").Value.String() 22 | commonTableFile := cmd.Flag("commonTable").Value.String() 23 | 24 | pf := viz.CreateRegexpFilter(pkgFilterFile) 25 | tf := viz.CreateRegexpFilter(tableFilterFile).AddExcludes(commonTableFile) 26 | 27 | if cmd.Flag("reverse").Value.String() == "true" { 28 | pkgFilter = pf.NotMatch 29 | tableFilter = tf.Match 30 | } else { 31 | pkgFilter = pf.Match 32 | tableFilter = tf.UnMatch 33 | } 34 | 35 | codeFiles := make([]string, 0) 36 | filepath.Walk(source, func(path string, fi os.FileInfo, err error) error { 37 | codeFiles = append(codeFiles, path) 38 | return nil 39 | }) 40 | 41 | allP := parseAllPkg(codeFiles) 42 | 43 | ps := make([]*viz.Procedure, 0) 44 | for name, p := range allP.Procedures { 45 | if pkgFilter(strings.Split(name, ".")[0]) { 46 | ps = append(ps, p) 47 | 48 | } 49 | } 50 | sort.Slice(ps, func(i, j int) bool { 51 | return strings.Compare(ps[i].Name, ps[j].Name) < 0 52 | }) 53 | 54 | for _, p := range ps { 55 | cs := make([]string, 0) 56 | for cname := range p.CallProcedures { 57 | if !pkgFilter(strings.Split(cname, ".")[0]) { 58 | cs = append(cs, cname) 59 | } 60 | } 61 | if len(cs) > 0 { 62 | fmt.Println(p.FullName) 63 | for _, cname := range cs { 64 | fmt.Printf(" %s\n", cname) 65 | } 66 | } 67 | } 68 | fmt.Println("-----------") 69 | for _, p := range ps { 70 | cs := make([]string, 0) 71 | for cname := range p.Tables { 72 | if tableFilter(strings.Split(cname, ".")[0]) { 73 | cs = append(cs, cname) 74 | } 75 | } 76 | if len(cs) > 0 { 77 | fmt.Println(p.FullName) 78 | for _, cname := range cs { 79 | fmt.Printf(" %s\n", cname) 80 | } 81 | } 82 | } 83 | 84 | }, 85 | } 86 | 87 | func init() { 88 | rootCmd.AddCommand(dbDepCmd) 89 | 90 | dbDepCmd.Flags().StringP("source", "s", "", "source code directory") 91 | dbDepCmd.Flags().StringP("table", "t", "table", "table filter file") 92 | dbDepCmd.Flags().StringP("commonTable", "c", "table_common", "common table file") 93 | dbDepCmd.Flags().StringP("package", "p", "pkg", "package filter file") 94 | dbDepCmd.Flags().BoolP("reverse", "R", false, "reverse dep") 95 | } 96 | -------------------------------------------------------------------------------- /viz/table.go: -------------------------------------------------------------------------------- 1 | package viz 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | type Table struct { 10 | Name string 11 | Count int 12 | } 13 | 14 | type AllTable struct { 15 | Tables map[string]*Table 16 | } 17 | 18 | func NewAllTable() *AllTable { 19 | return &AllTable{Tables: make(map[string]*Table)} 20 | } 21 | 22 | func (all *AllTable) Add(tableName string) { 23 | if _, ok := all.Tables[tableName]; !ok { 24 | all.Tables[tableName] = &Table{Name: tableName} 25 | } 26 | 27 | all.Tables[tableName].Count++ 28 | } 29 | 30 | func (all *AllTable) Print() { 31 | tables := make([]*Table, 0) 32 | for key := range all.Tables { 33 | tables = append(tables, all.Tables[key]) 34 | } 35 | sort.Slice(tables, func(i, j int) bool { 36 | return strings.Compare(tables[i].Name, tables[j].Name) < 0 37 | }) 38 | 39 | for _, table := range tables { 40 | fmt.Printf("%s : %d\n", table.Name, table.Count) 41 | } 42 | } 43 | 44 | type QueryTable struct { 45 | Name string 46 | Alias string 47 | Columns map[string]string 48 | } 49 | 50 | type Query struct { 51 | Sql string 52 | Tables map[string]*QueryTable 53 | } 54 | 55 | func NewQuery(sql string) *Query { 56 | return &Query{Sql: sql, Tables: make(map[string]*QueryTable)} 57 | } 58 | 59 | func (query *Query) AddTable(name, alias string) { 60 | query.Tables[name] = &QueryTable{Name: name, Alias: alias, Columns: make(map[string]string)} 61 | } 62 | 63 | func (query *Query) AddColumn(name string) { 64 | if !strings.Contains(name, ".") { 65 | for key := range query.Tables { 66 | query.Tables[key].Columns[name] = name + "?" 67 | return 68 | } 69 | } 70 | 71 | for _, qt := range query.Tables { 72 | alias := strings.ToUpper(qt.Alias) 73 | if strings.HasPrefix(strings.ToUpper(name), alias+".") { 74 | tmp := strings.Split(name, ".") 75 | qt.Columns[tmp[1]] = name 76 | return 77 | } 78 | if strings.Contains(strings.ToUpper(name), "("+alias+".") { 79 | qt.Columns[name] = name 80 | return 81 | } 82 | } 83 | } 84 | 85 | func (qt *QueryTable) Merge(other *QueryTable) { 86 | for key := range other.Columns { 87 | if _, ok := qt.Columns[key]; !ok { 88 | qt.Columns[key] = key 89 | } 90 | } 91 | } 92 | func (query *Query) Merge(other *Query) { 93 | for key, qt := range other.Tables { 94 | if _, ok := query.Tables[key]; !ok { 95 | query.Tables[key] = qt 96 | } else { 97 | query.Tables[key].Merge(qt) 98 | } 99 | } 100 | } 101 | 102 | func (query *Query) ToString() { 103 | for key, qt := range query.Tables { 104 | fmt.Println(key) 105 | columns := make([]string, 0) 106 | for key := range qt.Columns { 107 | columns = append(columns, key) 108 | } 109 | 110 | sort.Slice(columns, func(i, j int) bool { 111 | return strings.Compare(columns[i], columns[j]) < 0 112 | }) 113 | 114 | for _, key := range columns { 115 | fmt.Printf(" %s\n", key) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /viz/filter.go: -------------------------------------------------------------------------------- 1 | package viz 2 | 3 | import ( 4 | "bufio" 5 | "github.com/dlclark/regexp2" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type RegexpFilter struct { 11 | whiteList []string 12 | blackList []string 13 | excludes []string 14 | } 15 | 16 | func readFilterFile(fileName string) []string { 17 | result := make([]string, 0) 18 | f, _ := os.Open(fileName) 19 | scanner := bufio.NewScanner(f) 20 | scanner.Split(bufio.ScanLines) 21 | for scanner.Scan() { 22 | line := scanner.Text() 23 | if line != "" { 24 | result = append(result, strings.Trim(line, " ")) 25 | } 26 | } 27 | return result 28 | } 29 | 30 | func NewRegexpFilter() *RegexpFilter { 31 | return &RegexpFilter{ 32 | whiteList: make([]string, 0), 33 | blackList: make([]string, 0), 34 | excludes: make([]string, 0)} 35 | } 36 | 37 | func (r *RegexpFilter) AddExclude(exclude string) { 38 | r.excludes = append(r.excludes, exclude) 39 | } 40 | 41 | func (r *RegexpFilter) AddReg(reg string) { 42 | if strings.HasPrefix(reg, "- ") { 43 | r.blackList = append(r.blackList, reg[2:]) 44 | } else { 45 | r.whiteList = append(r.whiteList, reg) 46 | } 47 | } 48 | 49 | func (r *RegexpFilter) notExclude(s string) bool { 50 | for _, ct := range r.excludes { 51 | if ct == s { 52 | return false 53 | } 54 | } 55 | return true 56 | } 57 | 58 | func (r *RegexpFilter) Match(s string) bool { 59 | return r.notExclude(s) && r.matchWhiteList(s) && !r.matchBlackList(s) 60 | } 61 | 62 | func (r *RegexpFilter) NotMatch(s string) bool { 63 | return r.notExclude(s) && !r.Match(s) 64 | } 65 | 66 | func (r *RegexpFilter) UnMatch(s string) bool { 67 | return r.notExclude(s) && !r.matchWhiteList(s) && !r.matchBlackList(s) 68 | } 69 | 70 | func (r *RegexpFilter) matchWhiteList(s string) bool { 71 | for _, reg := range r.whiteList { 72 | re, _ := regexp2.Compile(reg, 0) 73 | if isMatch, _ := re.MatchString(s); isMatch { 74 | return true 75 | } 76 | } 77 | return false 78 | } 79 | 80 | func (r *RegexpFilter) matchBlackList(s string) bool { 81 | for _, reg := range r.blackList { 82 | re, _ := regexp2.Compile(reg, 0) 83 | if isMatch, _ := re.MatchString(s); isMatch { 84 | return true 85 | } 86 | } 87 | return false 88 | } 89 | 90 | func CreateRegexpFilter(fileName string) *RegexpFilter { 91 | rf := NewRegexpFilter() 92 | regs := readFilterFile(fileName) 93 | for _, reg := range regs { 94 | rf.AddReg(reg) 95 | } 96 | return rf 97 | } 98 | 99 | func (r *RegexpFilter) AddExcludes(fileName string) *RegexpFilter { 100 | r.excludes = readFilterFile(fileName) 101 | return r 102 | } 103 | 104 | type PrefixFilter struct { 105 | whiteList []string 106 | } 107 | 108 | 109 | func CreatePrefixFilter(fileName string) *PrefixFilter { 110 | pf := &PrefixFilter { whiteList: make([]string, 0)} 111 | lines := readFilterFile(fileName) 112 | for _, line := range lines { 113 | pf.whiteList = append(pf.whiteList, line) 114 | } 115 | return pf 116 | } 117 | 118 | func (p *PrefixFilter) Match(s string) bool { 119 | for _, prefix := range p.whiteList { 120 | if strings.HasPrefix(s, prefix) { 121 | return true 122 | } 123 | } 124 | return false 125 | } 126 | -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/tar.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "compress/gzip" 7 | "fmt" 8 | . "github.com/newlee/tequila/viz" 9 | "github.com/spf13/cobra" 10 | "io" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | func BytesCombine(pBytes ...[]byte) []byte { 16 | return bytes.Join(pBytes, []byte("")) 17 | } 18 | func processFile(srcFile string) { 19 | f, err := os.Open(srcFile) 20 | if err != nil { 21 | fmt.Println(err) 22 | os.Exit(1) 23 | } 24 | defer f.Close() 25 | 26 | gzf, err := gzip.NewReader(f) 27 | if err != nil { 28 | fmt.Println(err) 29 | os.Exit(1) 30 | } 31 | 32 | tarReader := tar.NewReader(gzf) 33 | 34 | i := 0 35 | buf := make([]byte, 1024*1024) 36 | buf2 := make([]byte, 1024*1024) 37 | 38 | for { 39 | header, err := tarReader.Next() 40 | 41 | if err == io.EOF { 42 | break 43 | } 44 | 45 | if err != nil { 46 | fmt.Println(err) 47 | os.Exit(1) 48 | } 49 | 50 | name := header.Name 51 | 52 | switch header.Typeflag { 53 | case tar.TypeDir: 54 | continue 55 | case tar.TypeReg: 56 | if strings.HasSuffix(name, "_icgraph.dot") { 57 | //fmt.Println("(", i, ")", "Name: ", name) 58 | len, err := tarReader.Read(buf) 59 | fbuf := buf[:len] 60 | if err == nil { 61 | for { 62 | len2, err := tarReader.Read(buf2) 63 | fbuf = BytesCombine(fbuf, buf2[:len2]) 64 | if err != nil { 65 | break 66 | } 67 | } 68 | } 69 | 70 | ParseICallGraphByBuffer(fbuf) 71 | } 72 | 73 | default: 74 | fmt.Printf("%s : %c %s %s\n", 75 | "Yikes! Unable to figure out type", 76 | header.Typeflag, 77 | "in file", 78 | name, 79 | ) 80 | } 81 | 82 | i++ 83 | } 84 | } 85 | 86 | var tarCmd *cobra.Command = &cobra.Command{ 87 | Use: "tar", 88 | Short: "full collaboration graph from tar file", 89 | Long: ``, 90 | Run: func(cmd *cobra.Command, args []string) { 91 | source := cmd.Flag("source").Value.String() 92 | 93 | //filter := cmd.Flag("filter").Value.String() 94 | ParseICallGraphStart() 95 | processFile(source) 96 | result := ParseICallGraphEnd() 97 | if cmd.Flag("findCrossRefs").Value.String() == "true" { 98 | crossRefs := result.FindCrossRef(MergeHeaderFunc) 99 | for _, cf := range crossRefs { 100 | fmt.Println(cf) 101 | } 102 | return 103 | } 104 | if cmd.Flag("mergePackage").Value.String() == "true" { 105 | result = result.MergeHeaderFile(MergePackageFunc) 106 | } 107 | if cmd.Flag("java").Value.String() != "" && cmd.Flag("common").Value.String() != "" { 108 | javaFilterFile := cmd.Flag("java").Value.String() 109 | commonFilterFile := cmd.Flag("common").Value.String() 110 | javaFilter := CreatePrefixFilter(javaFilterFile) 111 | 112 | commonFilter := CreatePrefixFilter(commonFilterFile) 113 | printRelation(result, javaFilter.Match, commonFilter.Match) 114 | return 115 | 116 | } 117 | 118 | result.ToDot(cmd.Flag("output").Value.String(), ".", func(s string) bool { 119 | return false 120 | }) 121 | }, 122 | } 123 | 124 | func init() { 125 | rootCmd.AddCommand(tarCmd) 126 | tarCmd.Flags().BoolP("findCrossRefs", "C", false, "find cross references") 127 | tarCmd.Flags().BoolP("mergePackage", "P", false, "merge package/folder for include dependencies") 128 | tarCmd.Flags().StringP("source", "s", "", "source code directory") 129 | tarCmd.Flags().StringP("filter", "f", "coll__graph.dot", "dot file filter") 130 | tarCmd.Flags().StringP("output", "o", "dep.dot", "output dot file name") 131 | tarCmd.Flags().StringP("java", "j", "", "java class filter") 132 | tarCmd.Flags().StringP("common", "c", "", "common java class") 133 | } 134 | -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/java_db.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/newlee/tequila/viz" 6 | "github.com/spf13/cobra" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | var pkgFilter func(line string) bool 13 | var tableFilter func(line string) bool 14 | var javaFilter func(line string) bool 15 | 16 | var javaDbCmd *cobra.Command = &cobra.Command{ 17 | Use: "jd", 18 | Short: "java code to database dependencies", 19 | Long: ``, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | source := cmd.Flag("source").Value.String() 22 | filterFile := cmd.Flag("filter").Value.String() 23 | pkgFilterFile := cmd.Flag("package").Value.String() 24 | tableFilterFile := cmd.Flag("table").Value.String() 25 | commonTableFile := cmd.Flag("commonTable").Value.String() 26 | 27 | pf := viz.CreateRegexpFilter(pkgFilterFile) 28 | tf := viz.CreateRegexpFilter(tableFilterFile).AddExcludes(commonTableFile) 29 | jf := viz.CreateRegexpFilter(filterFile) 30 | 31 | if cmd.Flag("reverse").Value.String() == "false" { 32 | pkgFilter = pf.NotMatch 33 | tableFilter = tf.UnMatch 34 | javaFilter = jf.Match 35 | } else { 36 | pkgFilter = pf.Match 37 | tableFilter = tf.Match 38 | javaFilter = jf.NotMatch 39 | } 40 | 41 | codeFiles := make([]string, 0) 42 | filepath.Walk(source, func(path string, fi os.FileInfo, err error) error { 43 | if !strings.HasSuffix(path, ".java") { 44 | return nil 45 | } 46 | if javaFilter(path) { 47 | codeFiles = append(codeFiles, path) 48 | } 49 | return nil 50 | }) 51 | 52 | allPkg := viz.NewAllPkg() 53 | tables := viz.NewAllTable() 54 | pkgCallerFiles := make(map[string]string) 55 | tableCallerFiles := make(map[string]string) 56 | 57 | doFiles(codeFiles, func() {}, func(line, codeFileName string) { 58 | line = strings.ToUpper(line) 59 | fields := strings.Fields(line) 60 | if len(fields) == 0 || isComment(fields[0]) { 61 | return 62 | } 63 | 64 | if strings.Contains(line, "PKG_") { 65 | doPkgLine(line, pkgFilter, func(pkg string, sp string) { 66 | split := strings.Split(codeFileName, "/") 67 | pkgCallerFiles[split[len(split)-1]] = "" 68 | allPkg.Add(pkg, sp) 69 | }) 70 | } 71 | 72 | if strings.Contains(line, " T_") || strings.Contains(line, ",T_") { 73 | doTableLine(line, tableFilter, func(table string) { 74 | split := strings.Split(codeFileName, "/") 75 | s := split[len(split)-1] 76 | if _, ok := tableCallerFiles[s]; !ok { 77 | tableCallerFiles[s] = "" 78 | } 79 | tableCallerFiles[s] = tableCallerFiles[s] + "," + table 80 | //tableCallerFiles[codeFileName] = "" 81 | tables.Add(table) 82 | }) 83 | } 84 | }) 85 | 86 | allPkg.Print() 87 | fmt.Println("") 88 | fmt.Println("-----------") 89 | for key := range pkgCallerFiles { 90 | fmt.Println(key) 91 | 92 | } 93 | fmt.Println("") 94 | fmt.Println("-----------") 95 | 96 | tables.Print() 97 | 98 | fmt.Println("") 99 | fmt.Println("-----------") 100 | for key, value := range tableCallerFiles { 101 | fmt.Println(key + " -- " + value) 102 | 103 | } 104 | }, 105 | } 106 | 107 | func init() { 108 | rootCmd.AddCommand(javaDbCmd) 109 | 110 | javaDbCmd.Flags().StringP("source", "s", "", "source code directory") 111 | javaDbCmd.Flags().StringP("filter", "f", "java", "file filter") 112 | javaDbCmd.Flags().StringP("table", "t", "table", "table filter file") 113 | javaDbCmd.Flags().StringP("commonTable", "c", "table_common", "common table file") 114 | javaDbCmd.Flags().StringP("package", "p", "pkg", "package filter file") 115 | javaDbCmd.Flags().BoolP("reverse", "R", false, "reverse dep") 116 | } 117 | -------------------------------------------------------------------------------- /dot/doxygen.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "github.com/awalterschulze/gographviz" 5 | "io/ioutil" 6 | "strings" 7 | ) 8 | 9 | type Node struct { 10 | Name string 11 | DstNodes []*Relation 12 | hasSrc bool 13 | } 14 | 15 | type Relation struct { 16 | Node *Node 17 | Style string 18 | } 19 | 20 | func (node *Node) removeGenericRelation(genericRelationMap map[string]*Node) { 21 | for index, relation := range node.DstNodes { 22 | if _, ok := genericRelationMap[relation.Node.Name]; ok { 23 | node.DstNodes[index] = relation.Node.DstNodes[0] 24 | } 25 | } 26 | for _, relation := range node.DstNodes { 27 | relation.Node.removeGenericRelation(genericRelationMap) 28 | } 29 | } 30 | 31 | func getMethodName(fullMethodName, split string, subDomainCallback func(string, string)) (string, bool) { 32 | if strings.Contains(fullMethodName, split) { 33 | tmp := strings.Split(fullMethodName, split) 34 | methodName := tmp[len(tmp)-1] 35 | methodName = strings.Replace(methodName, "\\l", "", -1) 36 | fullMethodName = strings.Replace(fullMethodName, "\\l", "", -1) 37 | subDomainCallback(fullMethodName, methodName) 38 | 39 | return methodName, true 40 | } 41 | return fullMethodName, false 42 | } 43 | 44 | func (node *Node) RemoveNS(fullNameCallback func(string, string)) { 45 | fullMethodName := node.Name 46 | if methodName, ok := getMethodName(fullMethodName, "::", fullNameCallback); ok { 47 | node.Name = methodName 48 | } else { 49 | node.Name, _ = getMethodName(fullMethodName, ".", fullNameCallback) 50 | } 51 | for _, relation := range node.DstNodes { 52 | relation.Node.RemoveNS(fullNameCallback) 53 | } 54 | } 55 | 56 | func (node *Node) IsIt(it string) bool { 57 | name := node.Name 58 | if methodName, ok := getMethodName(name, "::", func(s string, s2 string) {}); ok { 59 | name = methodName 60 | } else { 61 | name, _ = getMethodName(name, ".", func(s string, s2 string) {}) 62 | } 63 | return node.isIt(it) && name != it 64 | } 65 | 66 | func (node *Node) isIt(it string) bool { 67 | result := strings.HasSuffix(node.Name, it) 68 | if !result { 69 | for _, relation := range node.DstNodes { 70 | if relation.Style != "\"dashed\"" { 71 | return relation.Node.isIt(it) 72 | } 73 | } 74 | } 75 | 76 | return result 77 | } 78 | 79 | func ParseDoxygenFile(file string) *Node { 80 | fbuf, _ := ioutil.ReadFile(file) 81 | g, _ := gographviz.Read(fbuf) 82 | nodes := nodes(g, 1) 83 | 84 | nodeMap := make(map[string]*Node) 85 | genericRelationMap := make(map[string]*Node) 86 | for key := range g.Edges.DstToSrcs { 87 | for edgesKey := range g.Edges.DstToSrcs[key] { 88 | for _, edge := range g.Edges.DstToSrcs[key][edgesKey] { 89 | //doxygen use dir is "back" 90 | dst := nodes[edge.Src] 91 | src := nodes[edge.Dst] 92 | 93 | if _, ok := nodeMap[src]; !ok { 94 | nodeMap[src] = &Node{Name: src, DstNodes: make([]*Relation, 0)} 95 | } 96 | if _, ok := nodeMap[dst]; !ok { 97 | nodeMap[dst] = &Node{Name: dst, DstNodes: make([]*Relation, 0), hasSrc: true} 98 | } else { 99 | nodeMap[dst].hasSrc = true 100 | } 101 | 102 | nodeMap[src].DstNodes = append(nodeMap[src].DstNodes, 103 | &Relation{Node: nodeMap[dst], Style: edge.Attrs["style"]}) 104 | 105 | if strings.Contains(src, "\\<") && 106 | (strings.Contains(edge.Attrs["label"], "dummy_for_doxygen") || 107 | strings.Contains(edge.Attrs["label"], "elements")) { 108 | genericRelationMap[src] = nodeMap[src] 109 | } 110 | } 111 | } 112 | } 113 | var result *Node 114 | for key := range nodeMap { 115 | if !nodeMap[key].hasSrc { 116 | result = nodeMap[key] 117 | } 118 | } 119 | 120 | result.removeGenericRelation(genericRelationMap) 121 | return result 122 | } 123 | 124 | func nodes(g *gographviz.Graph, index int) map[string]string { 125 | nodes := make(map[string]string) 126 | for _, node := range g.Nodes.Nodes { 127 | fullMethodName := strings.Replace(node.Attrs["label"], "\"", "", 2) 128 | methodName := strings.Replace(fullMethodName, "\\l", "", -1) 129 | nodes[node.Name] = methodName 130 | } 131 | return nodes 132 | } 133 | -------------------------------------------------------------------------------- /dot/test.dot: -------------------------------------------------------------------------------- 1 | digraph "api::Api" 2 | { 3 | edge [fontname="Helvetica",fontsize="10",labelfontname="Helvetica",labelfontsize="10"]; 4 | node [fontname="Helvetica",fontsize="10",shape=record]; 5 | rankdir="LR"; 6 | Node1 [label="api::Api",height=0.2,width=0.4,color="black", fillcolor="grey75", style="filled", fontcolor="black"]; 7 | Node2 -> Node1 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" cargoService_" ,fontname="Helvetica"]; 8 | Node2 [label="std::shared_ptr\< services\l::CargoService \>",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$classstd_1_1shared__ptr.html"]; 9 | Node3 -> Node2 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" dummy_for_doxygen" ,fontname="Helvetica"]; 10 | Node3 [label="services::CargoService",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structservices_1_1_cargo_service.html"]; 11 | Node4 -> Node3 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" cargoRepository_" ,fontname="Helvetica"]; 12 | Node4 [label="std::shared_ptr\< repositories\l::CargoRepository \>",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$classstd_1_1shared__ptr.html"]; 13 | Node5 -> Node4 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" dummy_for_doxygen" ,fontname="Helvetica"]; 14 | Node5 [label="repositories::CargoRepository",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structrepositories_1_1_cargo_repository.html"]; 15 | Node6 -> Node5 [dir="back",color="midnightblue",fontsize="10",style="solid",fontname="Helvetica"]; 16 | Node6 [label="repositories::Repository",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structrepositories_1_1_repository.html"]; 17 | Node7 -> Node5 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" cargo_list" ,fontname="Helvetica"]; 18 | Node7 [label="std::vector\< domain\l::Cargo *\>",height=0.2,width=0.4,color="grey75", fillcolor="white", style="filled"]; 19 | Node8 -> Node7 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" elements\ndummy_for_doxygen" ,fontname="Helvetica"]; 20 | Node8 [label="domain::Cargo",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structdomain_1_1_cargo.html"]; 21 | Node9 -> Node8 [dir="back",color="midnightblue",fontsize="10",style="solid",fontname="Helvetica"]; 22 | Node9 [label="domain::AggregateRoot",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structdomain_1_1_aggregate_root.html"]; 23 | Node10 -> Node9 [dir="back",color="midnightblue",fontsize="10",style="solid",fontname="Helvetica"]; 24 | Node10 [label="domain::Entity",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structdomain_1_1_entity.html"]; 25 | Node11 -> Node8 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" product_list" ,fontname="Helvetica"]; 26 | Node11 [label="std::vector\< domain\l::Product *\>",height=0.2,width=0.4,color="grey75", fillcolor="white", style="filled"]; 27 | Node12 -> Node11 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" elements\ndummy_for_doxygen" ,fontname="Helvetica"]; 28 | Node12 [label="domain::Product",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structdomain_1_1_product.html"]; 29 | Node13 -> Node12 [dir="back",color="midnightblue",fontsize="10",style="solid",fontname="Helvetica"]; 30 | Node13 [label="domain::ValueObject",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structdomain_1_1_value_object.html"]; 31 | Node14 -> Node8 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" delivery" ,fontname="Helvetica"]; 32 | Node14 [label="domain::Delivery",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structdomain_1_1_delivery.html"]; 33 | Node13 -> Node14 [dir="back",color="midnightblue",fontsize="10",style="solid",fontname="Helvetica"]; 34 | Node15 -> Node3 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" cargoProvider_" ,fontname="Helvetica"]; 35 | Node15 [label="std::shared_ptr\< services\l::CargoProvider \>",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$classstd_1_1shared__ptr.html"]; 36 | Node16 -> Node15 [dir="back",color="darkorchid3",fontsize="10",style="dashed",label=" dummy_for_doxygen" ,fontname="Helvetica"]; 37 | Node16 [label="services::CargoProvider",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structservices_1_1_cargo_provider.html"]; 38 | Node17 -> Node16 [dir="back",color="midnightblue",fontsize="10",style="solid",fontname="Helvetica"]; 39 | Node17 [label="services::Provider",height=0.2,width=0.4,color="black", fillcolor="white", style="filled",URL="$structservices_1_1_provider.html"]; 40 | } 41 | -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/coll.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | //"fmt" 5 | "fmt" 6 | . "github.com/newlee/tequila/viz" 7 | "github.com/spf13/cobra" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | var collCmd *cobra.Command = &cobra.Command{ 13 | Use: "coll", 14 | Short: "full collaboration grpahh", 15 | Long: ``, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | source := cmd.Flag("source").Value.String() 18 | filter := cmd.Flag("filter").Value.String() 19 | ignore := cmd.Flag("ignore").Value.String() 20 | result := ParseColl(source, filter) 21 | 22 | if cmd.Flag("mergePackage").Value.String() == "true" { 23 | Level, _ = strconv.Atoi(cmd.Flag("mergeLevel").Value.String()) 24 | 25 | result = result.MergeHeaderFile(MergePackageFunc) 26 | } 27 | if cmd.Flag("entryPoints").Value.String() == "true" { 28 | entryPoints := result.EntryPoints(MergePackageFunc) 29 | for _, cf := range entryPoints { 30 | fmt.Println(cf) 31 | } 32 | return 33 | } 34 | 35 | if cmd.Flag("fanInFanOut").Value.String() == "true" { 36 | fans := result.SortedByFan(MergePackageFunc) 37 | fmt.Println("Name\tTotal\tFan-In\tFan-Out") 38 | for _, fan := range fans { 39 | fmt.Printf("%s\t%v\t%v\t%v\n", fan.Name, fan.FanIn+fan.FanOut, fan.FanIn, fan.FanOut) 40 | } 41 | return 42 | } 43 | 44 | ignores := strings.Split(ignore, ",") 45 | var nodeFilter = func(key string) bool { 46 | for _, f := range ignores { 47 | if key == f { 48 | return true 49 | } 50 | } 51 | return false 52 | } 53 | if cmd.Flag("java").Value.String() != "" && cmd.Flag("common").Value.String() != "" { 54 | javaFilterFile := cmd.Flag("java").Value.String() 55 | commonFilterFile := cmd.Flag("common").Value.String() 56 | javaFilter := CreatePrefixFilter(javaFilterFile) 57 | 58 | commonFilter := CreatePrefixFilter(commonFilterFile) 59 | printRelation(result, javaFilter.Match, commonFilter.Match) 60 | return 61 | 62 | } 63 | result.ToDot(cmd.Flag("output").Value.String(), ".", nodeFilter) 64 | }, 65 | } 66 | 67 | func init() { 68 | rootCmd.AddCommand(collCmd) 69 | 70 | collCmd.Flags().StringP("source", "s", "", "source code directory") 71 | collCmd.Flags().StringP("filter", "f", "coll__graph.dot", "dot file filter") 72 | collCmd.Flags().StringP("ignore", "i", "main.cpp,main", "ignore") 73 | collCmd.Flags().StringP("output", "o", "dep.dot", "output dot file name") 74 | collCmd.Flags().BoolP("entryPoints", "E", false, "list entry points") 75 | collCmd.Flags().BoolP("fanInFanOut", "F", false, "sorted fan-in and fan-out") 76 | collCmd.Flags().BoolP("mergePackage", "P", false, "merge package/folder for include dependencies") 77 | collCmd.Flags().Int32P("mergeLevel", "L", 3, "merge package/folder level") 78 | collCmd.Flags().StringP("java", "j", "", "java class filter") 79 | collCmd.Flags().StringP("common", "c", "", "common java class") 80 | 81 | } 82 | 83 | func printRelation(f *FullGraph, javaFilter func(string) bool, commonFilter func(string) bool) { 84 | 85 | class2other, other2class, class2common, other2common, common2 := make([]*Relation, 0), make([]*Relation, 0), make([]*Relation, 0), make([]*Relation, 0), make([]*Relation, 0) 86 | 87 | var isOther = func(s string) bool { 88 | return !javaFilter(s) && !commonFilter(s) 89 | } 90 | for _, relation := range f.RelationList { 91 | if !strings.Contains(relation.To, ".") { 92 | continue 93 | } 94 | if javaFilter(relation.From) && !javaFilter(relation.To) { 95 | if commonFilter(relation.To) { 96 | class2common = append(class2common, relation) 97 | } 98 | if !commonFilter(relation.To) { 99 | class2other = append(class2other, relation) 100 | } 101 | } 102 | 103 | if isOther(relation.From) && !isOther(relation.To) { 104 | if javaFilter(relation.To) { 105 | other2class = append(other2class, relation) 106 | } 107 | if commonFilter(relation.To) { 108 | other2common = append(other2common, relation) 109 | } 110 | } 111 | 112 | if commonFilter(relation.From) && !commonFilter(relation.To) { 113 | common2 = append(common2, relation) 114 | } 115 | } 116 | 117 | fmt.Println("class2other:") 118 | for _, r := range class2other { 119 | fmt.Printf("%s -> %s\n", r.From, r.To) 120 | } 121 | fmt.Println("-----------") 122 | fmt.Println("") 123 | fmt.Println("other2class:") 124 | for _, r := range other2class { 125 | fmt.Printf("%s -> %s\n", r.From, r.To) 126 | } 127 | fmt.Println("-----------") 128 | fmt.Println("") 129 | fmt.Println("class2common:") 130 | for _, r := range class2common { 131 | fmt.Printf("%s -> %s\n", r.From, r.To) 132 | } 133 | fmt.Println("-----------") 134 | fmt.Println("") 135 | fmt.Println("other2common:") 136 | for _, r := range other2common { 137 | fmt.Printf("%s -> %s\n", r.From, r.To) 138 | } 139 | fmt.Println("-----------") 140 | fmt.Println("") 141 | fmt.Println("common2...:") 142 | for _, r := range common2 { 143 | fmt.Printf("%s -> %s\n", r.From, r.To) 144 | } 145 | fmt.Println("-----------") 146 | 147 | } 148 | -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/db_chain.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/awalterschulze/gographviz" 7 | "github.com/newlee/tequila/viz" 8 | "github.com/spf13/cobra" 9 | "os" 10 | "path/filepath" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | func parseAllPkg(codeFiles []string) *viz.AllProcedure { 17 | allP := viz.NewAllProcedure() 18 | pkg := "" 19 | doFiles(codeFiles, func() { 20 | pkg = "" 21 | }, func(line, codeFileName string) { 22 | line = strings.ToUpper(line) 23 | 24 | if strings.Contains(line, "PKG_") && strings.Contains(line, "CREATE") { 25 | doCreatePkg(line, func(s string) { 26 | pkg = s 27 | }) 28 | } 29 | 30 | if strings.Contains(line, "PROCEDURE") && strings.Contains(line, "P_") { 31 | doCreateProcedure(line, func(s string) { 32 | allP.Add(pkg, s) 33 | }) 34 | } 35 | }) 36 | procedure := "" 37 | isComments := false 38 | 39 | doFiles(codeFiles, func() { 40 | pkg = "" 41 | procedure = "" 42 | isComments = false 43 | }, func(line, codeFileName string) { 44 | line = strings.ToUpper(line) 45 | line = strings.Trim(line, " ") 46 | if strings.HasPrefix(line, "/*") { 47 | isComments = true 48 | } 49 | 50 | if strings.HasSuffix(line, "*/") || strings.HasSuffix(line, "*/;") { 51 | isComments = false 52 | } 53 | 54 | if isComments { 55 | return 56 | } 57 | 58 | if strings.HasPrefix(line, "--") { 59 | return 60 | } 61 | 62 | if strings.Contains(line, "PKG_") && strings.Contains(line, "CREATE") { 63 | doCreatePkg(line, func(s string) { 64 | pkg = s 65 | }) 66 | } 67 | 68 | if strings.Contains(line, "PROCEDURE") && strings.Contains(line, "P_") { 69 | doCreateProcedure(line, func(s string) { 70 | procedure = s 71 | }) 72 | } 73 | 74 | if strings.Contains(line, "PKG_") && strings.Contains(line, "(") { 75 | doPkgLine(line, emptyFilter, func(p string, sp string) { 76 | allP.AddCall(pkg, procedure, p, sp) 77 | }) 78 | } 79 | 80 | if strings.Contains(line, "P_") && strings.Contains(line, "(") { 81 | doCreateProcedure(line, func(s string) { 82 | allP.AddCall(pkg, procedure, pkg, s) 83 | }) 84 | } 85 | 86 | if strings.Contains(line, " T_") || strings.Contains(line, ",T_") { 87 | doSplit(line, tableSplit, func(table string) { 88 | if strings.HasPrefix(table, "T_") && !viz.IsChineseChar(table) && !strings.Contains(table, ";") && !strings.Contains(table, "、") { 89 | isWrite := strings.Contains(line, "INSERT ") || strings.Contains(line, "UPDATE ") || strings.Contains(line, "DELETE ") 90 | allP.AddTable(pkg, procedure, table, isWrite) 91 | } 92 | }) 93 | } 94 | }) 95 | 96 | return allP 97 | } 98 | 99 | var DbChainCmd *cobra.Command = &cobra.Command{ 100 | Use: "dc", 101 | Short: "database call chain grpah", 102 | Long: ``, 103 | Run: func(cmd *cobra.Command, args []string) { 104 | source := cmd.Flag("source").Value.String() 105 | point := cmd.Flag("point").Value.String() 106 | codeFiles := make([]string, 0) 107 | filepath.Walk(source, func(path string, fi os.FileInfo, err error) error { 108 | codeFiles = append(codeFiles, path) 109 | return nil 110 | }) 111 | 112 | allP := parseAllPkg(codeFiles) 113 | 114 | pTree, pTables := allP.Print(point) 115 | 116 | tables := make([]string, 0) 117 | for key, rw := range pTables { 118 | tables = append(tables, fmt.Sprintf("%s [%s]", key, rw.ToString())) 119 | } 120 | sort.Slice(tables, func(i, j int) bool { 121 | return strings.Compare(tables[i], tables[j]) < 0 122 | }) 123 | 124 | for _, t := range tables { 125 | fmt.Printf("%s\n", t) 126 | } 127 | graph := gographviz.NewGraph() 128 | graph.SetName("G") 129 | 130 | nodeIndex := 1 131 | nodes := make(map[string]string) 132 | 133 | for key := range pTree { 134 | tmp := strings.Split(key, " -> ") 135 | if cmd.Flag("mergePackage").Value.String() == "true" { 136 | nodes[strings.Split(tmp[0], ".")[0]] = "" 137 | nodes[strings.Split(tmp[1], ".")[0]] = "" 138 | } else { 139 | nodes[tmp[0]] = "" 140 | nodes[tmp[1]] = "" 141 | } 142 | } 143 | 144 | owner := cmd.Flag("key").Value.String() 145 | for node := range nodes { 146 | attrs := make(map[string]string) 147 | attrs["label"] = "\"" + node + "\"" 148 | attrs["shape"] = "box" 149 | if owner != "" { 150 | if strings.Contains(node, owner) { 151 | attrs["color"] = "greenyellow" 152 | attrs["style"] = "filled" 153 | } else { 154 | attrs["color"] = "orange" 155 | attrs["style"] = "filled" 156 | } 157 | } 158 | 159 | graph.AddNode("G", "node"+strconv.Itoa(nodeIndex), attrs) 160 | nodes[node] = "node" + strconv.Itoa(nodeIndex) 161 | nodeIndex++ 162 | } 163 | relations := make(map[string]string) 164 | for key := range pTree { 165 | attrs := make(map[string]string) 166 | tmp := strings.Split(key, " -> ") 167 | if cmd.Flag("mergePackage").Value.String() == "true" { 168 | pName0 := strings.Split(tmp[0], ".")[0] 169 | pName1 := strings.Split(tmp[1], ".")[0] 170 | 171 | if pName0 != pName1 { 172 | if _, ok := relations[pName0+pName1]; !ok { 173 | relations[pName0+pName1] = "" 174 | graph.AddEdge(nodes[pName0], nodes[pName1], true, attrs) 175 | } 176 | } 177 | } else { 178 | graph.AddEdge(nodes[tmp[0]], nodes[tmp[1]], true, attrs) 179 | } 180 | } 181 | 182 | f, _ := os.Create(cmd.Flag("output").Value.String()) 183 | w := bufio.NewWriter(f) 184 | w.WriteString("di" + graph.String()) 185 | w.Flush() 186 | 187 | }, 188 | } 189 | 190 | func init() { 191 | rootCmd.AddCommand(DbChainCmd) 192 | 193 | DbChainCmd.Flags().StringP("source", "s", "", "source code directory") 194 | DbChainCmd.Flags().StringP("point", "p", "", "input point") 195 | DbChainCmd.Flags().StringP("key", "k", "", "owner key") 196 | DbChainCmd.Flags().StringP("output", "o", "tree.dot", "output dot file name") 197 | DbChainCmd.Flags().BoolP("mergePackage", "P", false, "merge package/folder for include dependencies") 198 | } 199 | -------------------------------------------------------------------------------- /viz/pkg.go: -------------------------------------------------------------------------------- 1 | package viz 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | func IsChineseChar(str string) bool { 11 | for _, r := range str { 12 | if unicode.Is(unicode.Scripts["Han"], r) { 13 | return true 14 | } 15 | } 16 | return false 17 | } 18 | 19 | type RW struct { 20 | R bool 21 | W bool 22 | } 23 | 24 | func (rw *RW) merge(rw2 *RW) { 25 | if !rw.R && rw2.R { 26 | rw.R = true 27 | } 28 | 29 | if !rw.W && rw2.W { 30 | rw.W = true 31 | } 32 | } 33 | 34 | func (rw *RW) ToString() string { 35 | if rw.R && rw.W { 36 | return "R,W" 37 | } 38 | 39 | if rw.R { 40 | return "R" 41 | } 42 | 43 | if rw.W { 44 | return "W" 45 | } 46 | return "" 47 | } 48 | 49 | type Procedure struct { 50 | Name string 51 | FullName string 52 | Count int 53 | CallProcedures map[string]*Procedure 54 | BePrint bool 55 | Tables map[string]*RW 56 | } 57 | 58 | type Pkg struct { 59 | Name string 60 | Procedures map[string]*Procedure 61 | } 62 | 63 | type AllPkg struct { 64 | Pkgs map[string]*Pkg 65 | } 66 | 67 | type AllProcedure struct { 68 | Procedures map[string]*Procedure 69 | } 70 | 71 | func NewAllPkg() *AllPkg { 72 | return &AllPkg{Pkgs: make(map[string]*Pkg)} 73 | } 74 | 75 | func NewAllProcedure() *AllProcedure { 76 | return &AllProcedure{Procedures: make(map[string]*Procedure)} 77 | } 78 | 79 | func (all *AllPkg) Add(pkgName, procedure string) { 80 | if _, ok := all.Pkgs[pkgName]; !ok { 81 | all.Pkgs[pkgName] = &Pkg{Name: pkgName, Procedures: make(map[string]*Procedure)} 82 | } 83 | 84 | pkg := all.Pkgs[pkgName] 85 | if _, ok := pkg.Procedures[procedure]; !ok { 86 | pkg.Procedures[procedure] = &Procedure{Name: procedure, Count: 0} 87 | } 88 | 89 | pkg.Procedures[procedure].Count++ 90 | } 91 | 92 | func (all *AllProcedure) Add(pkgName, procedure string) { 93 | fullName := procedure 94 | if pkgName != "" { 95 | fullName = pkgName + "." + procedure 96 | } 97 | 98 | if _, ok := all.Procedures[fullName]; !ok { 99 | all.Procedures[fullName] = &Procedure{ 100 | Name: procedure, 101 | FullName: fullName, 102 | CallProcedures: make(map[string]*Procedure), 103 | Tables: make(map[string]*RW)} 104 | } 105 | } 106 | 107 | func (all *AllProcedure) AddCall(pkgName, procedure, callPkgName, callProcedure string) { 108 | fullName := procedure 109 | if pkgName != "" { 110 | fullName = pkgName + "." + procedure 111 | } 112 | cFullName := callProcedure 113 | if callPkgName != "" { 114 | cFullName = callPkgName + "." + callProcedure 115 | } 116 | 117 | if _, ok := all.Procedures[fullName]; ok { 118 | p := all.Procedures[fullName] 119 | if _, ok := all.Procedures[cFullName]; ok { 120 | p.CallProcedures[cFullName] = all.Procedures[cFullName] 121 | } 122 | } 123 | } 124 | 125 | func (all *AllProcedure) AddTable(pkgName, procedure, table string, isWrite bool) { 126 | fullName := procedure 127 | if pkgName != "" { 128 | fullName = pkgName + "." + procedure 129 | } 130 | 131 | if _, ok := all.Procedures[fullName]; ok { 132 | p := all.Procedures[fullName] 133 | tables := p.Tables 134 | if _, ok := tables[table]; !ok { 135 | tables[table] = &RW{} 136 | } 137 | if isWrite { 138 | tables[table].W = true 139 | } else { 140 | tables[table].R = true 141 | } 142 | } 143 | } 144 | 145 | func (all *AllPkg) Exist(name string) bool { 146 | if _, ok := all.Pkgs[name]; ok { 147 | return true 148 | } 149 | 150 | return false 151 | } 152 | 153 | func (all *AllPkg) ExistSp(name string, procedure string) bool { 154 | if _, ok := all.Pkgs[name]; ok { 155 | if _, ok := all.Pkgs[name].Procedures[procedure]; ok { 156 | return true 157 | } 158 | } 159 | 160 | return false 161 | } 162 | 163 | func (all *AllPkg) Print() { 164 | pkgs := make([]*Pkg, 0) 165 | for key := range all.Pkgs { 166 | pkgs = append(pkgs, all.Pkgs[key]) 167 | } 168 | sort.Slice(pkgs, func(i, j int) bool { 169 | return strings.Compare(pkgs[i].Name, pkgs[j].Name) < 0 170 | }) 171 | 172 | for _, pkg := range pkgs { 173 | pkg.Print() 174 | } 175 | } 176 | 177 | var pTree map[string]string 178 | var pTables map[string]*RW 179 | var checkTables = make(map[string]string) 180 | 181 | func (all *AllProcedure) Print(fullName string) (map[string]string, map[string]*RW) { 182 | pTree = make(map[string]string) 183 | pTables = make(map[string]*RW) 184 | if _, ok := all.Procedures[fullName]; ok { 185 | all.Procedures[fullName].Print(fullName) 186 | } 187 | fmt.Println(len(pTree)) 188 | for _, v := range checkTables { 189 | fmt.Println(v) 190 | } 191 | return pTree, pTables 192 | } 193 | 194 | var tables = make(map[string]string) 195 | 196 | func (p *Procedure) Print(fullName string) { 197 | if p.BePrint { 198 | return 199 | } 200 | p.BePrint = true 201 | for table, rw := range p.Tables { 202 | if rw.W { 203 | if _, ok := tables[table]; !ok { 204 | tables[table] = "" 205 | } 206 | tables[table] = tables[table] + "," + p.FullName 207 | } 208 | 209 | if rw.R { 210 | if _, ok := tables[table]; ok && tables[table] != (","+p.FullName) { 211 | checkTables[table] = fmt.Sprintf("%s: \nwrite by: %s\nread by: %s\n", table, tables[table], p.FullName) 212 | } 213 | 214 | } 215 | if _, ok := pTables[table]; !ok { 216 | pTables[table] = rw 217 | } else { 218 | pTables[table].merge(rw) 219 | } 220 | 221 | } 222 | 223 | for key, procedure := range p.CallProcedures { 224 | if key != fullName { 225 | pTree[fmt.Sprintf("%s -> %s", fullName, key)] = "" 226 | procedure.Print(key) 227 | } 228 | } 229 | } 230 | 231 | func (pkg *Pkg) Print() { 232 | procedures := make([]*Procedure, 0) 233 | count := 0 234 | for key := range pkg.Procedures { 235 | procedure := pkg.Procedures[key] 236 | procedures = append(procedures, procedure) 237 | count += procedure.Count 238 | } 239 | sort.Slice(procedures, func(i, j int) bool { 240 | return strings.Compare(procedures[i].Name, procedures[j].Name) < 0 241 | }) 242 | 243 | fmt.Printf("%s : %d\n", pkg.Name, count) 244 | for _, procedure := range procedures { 245 | fmt.Printf(" %s : %d\n", procedure.Name, procedure.Count) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /model/problem.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type SubDomain struct { 8 | ARs map[string]*Entity 9 | Repos map[string]*Repository 10 | Providers map[string]*Provider 11 | es map[string]*Entity 12 | vos map[string]*ValueObject 13 | } 14 | type ProblemModel struct { 15 | SubDomains map[string]*SubDomain 16 | } 17 | 18 | func (model *ProblemModel) Validate() bool { 19 | for key := range model.SubDomains { 20 | if !model.SubDomains[key].Validate() { 21 | return false 22 | } 23 | } 24 | return true 25 | } 26 | 27 | func (model *ProblemModel) Compare(other *ProblemModel) error { 28 | if len(model.SubDomains) != len(other.SubDomains) { 29 | return errors.New("diff subdomain number") 30 | } 31 | 32 | for key := range model.SubDomains { 33 | domain := model.SubDomains[key] 34 | if !domain.Compare(other.SubDomains[key]) { 35 | return errors.New("subdomain: " + key + "is diff") 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func (subDomain *SubDomain) Validate() bool { 43 | entityMap := make(map[string]int) 44 | for key := range subDomain.ARs { 45 | ar := subDomain.ARs[key] 46 | for _, entity := range ar.Entities { 47 | if _, ok := entityMap[entity.name]; !ok { 48 | entityMap[entity.name] = 0 49 | } 50 | entityMap[entity.name] = entityMap[entity.name] + 1 51 | } 52 | } 53 | for key := range entityMap { 54 | if entityMap[key] > 1 { 55 | return false 56 | } 57 | } 58 | return true 59 | } 60 | 61 | func (subDomain *SubDomain) Compare(other *SubDomain) bool { 62 | if len(subDomain.ARs) != len(other.ARs) { 63 | return false 64 | } 65 | if len(subDomain.Repos) != len(other.Repos) { 66 | return false 67 | } 68 | for key := range subDomain.ARs { 69 | ar := subDomain.ARs[key] 70 | if !ar.Compare(other.ARs[key]) { 71 | return false 72 | } 73 | } 74 | for key := range subDomain.Repos { 75 | repo := subDomain.Repos[key] 76 | if !repo.Compare(other.Repos[key]) { 77 | return false 78 | } 79 | } 80 | return true 81 | } 82 | 83 | func (subDomain *SubDomain) AddNode(cms *CommentMappingList, name, comment string) { 84 | for _, cm := range *cms { 85 | if cm.comment == comment { 86 | cm.mapping(subDomain, name) 87 | break 88 | } 89 | } 90 | } 91 | 92 | type SubDomainWhenThen struct { 93 | subDomain *SubDomain 94 | isMatch bool 95 | current interface{} 96 | src string 97 | dsts []string 98 | } 99 | 100 | func (subDomain *SubDomain) given(src string, dsts []string) *SubDomainWhenThen { 101 | return &SubDomainWhenThen{ 102 | subDomain: subDomain, 103 | src: src, 104 | dsts: dsts, 105 | } 106 | } 107 | 108 | func (whenThen *SubDomainWhenThen) when(fined interface{}, ok bool) *SubDomainWhenThen { 109 | whenThen.isMatch = ok 110 | if ok { 111 | whenThen.current = fined 112 | } 113 | 114 | return whenThen 115 | } 116 | 117 | func (whenThen *SubDomainWhenThen) whenRepo() *SubDomainWhenThen { 118 | repo, ok := whenThen.subDomain.Repos[whenThen.src] 119 | return whenThen.when(repo, ok) 120 | } 121 | 122 | func (whenThen *SubDomainWhenThen) whenEntity() *SubDomainWhenThen { 123 | entity, ok := whenThen.subDomain.es[whenThen.src] 124 | return whenThen.when(entity, ok) 125 | } 126 | func (whenThen *SubDomainWhenThen) whenAggregateRoot() *SubDomainWhenThen { 127 | ar, ok := whenThen.subDomain.ARs[whenThen.src] 128 | return whenThen.when(ar, ok) 129 | } 130 | 131 | func (whenThen *SubDomainWhenThen) thenAdd(addRelations func(interface{}, []string)) *SubDomainWhenThen { 132 | if whenThen.isMatch { 133 | addRelations(whenThen.current, whenThen.dsts) 134 | } 135 | return whenThen 136 | } 137 | 138 | func (subDomain *SubDomain) AddRelations(src string, dsts []string) { 139 | subDomain.given(src, dsts). 140 | whenAggregateRoot().thenAdd(subDomain.addAggregateRootRelations). 141 | whenEntity().thenAdd(subDomain.addEntityRelations). 142 | whenRepo().thenAdd(subDomain.addRepoRelations) 143 | } 144 | 145 | func (subDomain *SubDomain) addRepoRelations(repo interface{}, dsts []string) { 146 | for _, dst := range dsts { 147 | repo.(*Repository).For = dst 148 | } 149 | } 150 | func (subDomain *SubDomain) addEntityRelations(entity interface{}, dsts []string) { 151 | for _, dst := range dsts { 152 | _entity := entity.(*Entity) 153 | if et, ok := subDomain.es[dst]; ok { 154 | _entity.Entities = append(_entity.Entities, et) 155 | } 156 | if vo, ok := subDomain.vos[dst]; ok { 157 | _entity.VOs = append(_entity.VOs, vo) 158 | } 159 | } 160 | } 161 | func (subDomain *SubDomain) addAggregateRootRelations(ar interface{}, dsts []string) { 162 | for _, dst := range dsts { 163 | _ar := ar.(*Entity) 164 | if ref, ok := subDomain.ARs[dst]; ok { 165 | _ar.Refs = append(_ar.Refs, ref) 166 | } 167 | if et, ok := subDomain.es[dst]; ok { 168 | _ar.Entities = append(_ar.Entities, et) 169 | } 170 | if vo, ok := subDomain.vos[dst]; ok { 171 | _ar.VOs = append(_ar.VOs, vo) 172 | } 173 | } 174 | } 175 | 176 | func NewSubDomain() *SubDomain { 177 | return &SubDomain{ 178 | ARs: make(map[string]*Entity), 179 | Repos: make(map[string]*Repository), 180 | Providers: make(map[string]*Provider), 181 | es: make(map[string]*Entity), 182 | vos: make(map[string]*ValueObject), 183 | } 184 | } 185 | 186 | type CommentMapping struct { 187 | comment string 188 | mapping func(domain *SubDomain, name string) 189 | } 190 | 191 | type CommentMappingList []*CommentMapping 192 | 193 | var addAggregateRootFunc = func(subDomain *SubDomain, name string) { 194 | subDomain.ARs[name] = NewEntity(name) 195 | } 196 | var addEntityFunc = func(subDomain *SubDomain, name string) { 197 | subDomain.es[name] = NewEntity(name) 198 | } 199 | var addValueObjectFunc = func(subDomain *SubDomain, name string) { 200 | subDomain.vos[name] = NewValueObject(name) 201 | } 202 | 203 | var addRepoFunc = func(subDomain *SubDomain, name string) { 204 | subDomain.Repos[name] = NewRepository(name) 205 | } 206 | var addProviderFunc = func(subDomain *SubDomain, name string) { 207 | subDomain.Providers[name] = NewProvider(name) 208 | } 209 | 210 | func InitCommentMapping() *CommentMappingList { 211 | return &CommentMappingList{ 212 | {comment: "AR", mapping: addAggregateRootFunc}, 213 | {comment: "E", mapping: addEntityFunc}, 214 | {comment: "VO", mapping: addValueObjectFunc}, 215 | {comment: "Repo", mapping: addRepoFunc}, 216 | {comment: "Provider", mapping: addProviderFunc}, 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /doxygen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/newlee/tequila/dot" 5 | . "github.com/newlee/tequila/model" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | type parseResult struct { 12 | es map[string]*Entity 13 | vos map[string]*ValueObject 14 | } 15 | 16 | var codeArs = make(map[string]*Entity) 17 | var repos = make(map[string]*Repository) 18 | var services = make(map[string]*Service) 19 | var providers = make(map[string]*Provider) 20 | var subDomainMap = make(map[string][]string) 21 | var layerMap = make(map[string][]string) 22 | 23 | func isAggregateRoot(className string) bool { 24 | return className == "AggregateRoot" 25 | } 26 | 27 | func codeDotFiles(codeDir string) []string { 28 | codeDotFiles := make([]string, 0) 29 | filepath.Walk(codeDir, func(path string, fi os.FileInfo, err error) error { 30 | if strings.HasSuffix(path, ".dot") { 31 | if strings.HasSuffix(path, "class_domain_1_1_aggregate_root__coll__graph.dot") { 32 | return nil 33 | } 34 | if strings.Contains(path, "inherit_") { 35 | return nil 36 | } 37 | if strings.HasSuffix(path, "_cgraph.dot") { 38 | return nil 39 | } 40 | codeDotFiles = append(codeDotFiles, path) 41 | } 42 | 43 | return nil 44 | }) 45 | 46 | return codeDotFiles 47 | } 48 | 49 | func isAR(node *Node) bool { 50 | return node.IsIt("AggregateRoot") 51 | } 52 | func isEntity(node *Node) bool { 53 | return node.IsIt("Entity") 54 | } 55 | 56 | func isValueObject(node *Node) bool { 57 | return node.IsIt("ValueObject") 58 | } 59 | func isRepo(node *Node) bool { 60 | return node.IsIt("Repository") 61 | } 62 | 63 | func isProvider(node *Node) bool { 64 | return node.IsIt("Provider") && !strings.HasPrefix(node.Name, "Stub") && !strings.HasPrefix(node.Name, "Fake") 65 | } 66 | 67 | func isSerice(node *Node) bool { 68 | return strings.HasSuffix(node.Name, "Service") 69 | } 70 | 71 | func (result *parseResult) parseNode(node *Node, todo func(*Node, *parseResult)) *parseResult { 72 | todo(node, result) 73 | for _, relation := range node.DstNodes { 74 | result.parseNode(relation.Node, todo) 75 | } 76 | return result 77 | } 78 | 79 | func parseRelation(node *Node, result *parseResult) { 80 | src := node.Name 81 | if !isAggregateRoot(src) { 82 | isAr := isAR(node) 83 | if isAr { 84 | codeArs[src] = NewEntity(src) 85 | } 86 | if isEntity(node) && !isAr { 87 | result.es[src] = NewEntity(src) 88 | } 89 | if isValueObject(node) { 90 | result.vos[src] = NewValueObject(src) 91 | } 92 | if isSerice(node) { 93 | services[src] = NewService(src) 94 | } 95 | if isRepo(node) { 96 | repos[src] = NewRepository(src) 97 | } 98 | if isProvider(node) { 99 | providers[src] = NewProvider(src) 100 | } 101 | } 102 | } 103 | 104 | func parseAR(node *Node, result *parseResult) { 105 | if ar, ok := codeArs[node.Name]; ok { 106 | for _, relation := range node.DstNodes { 107 | dst := relation.Node.Name 108 | 109 | if ref, ok := codeArs[dst]; ok { 110 | ar.Refs = append(ar.Refs, ref) 111 | } 112 | if et, ok := result.es[dst]; ok { 113 | ar.Entities = append(ar.Entities, et) 114 | } 115 | if vo, ok := result.vos[dst]; ok { 116 | ar.VOs = append(ar.VOs, vo) 117 | } 118 | } 119 | } 120 | } 121 | 122 | func parseEntity(node *Node, result *parseResult) { 123 | if entity, ok := result.es[node.Name]; ok { 124 | for _, relation := range node.DstNodes { 125 | dst := relation.Node.Name 126 | if et, ok := result.es[dst]; ok { 127 | entity.Entities = append(entity.Entities, et) 128 | } 129 | if vo, ok := result.vos[dst]; ok { 130 | entity.AppendVO(vo) 131 | } 132 | } 133 | } 134 | } 135 | func parseRepo(node *Node, result *parseResult) { 136 | if repo, ok := repos[node.Name]; ok { 137 | for _, relation := range node.DstNodes { 138 | dst := relation.Node.Name 139 | if dst != "Repository" { 140 | repo.For = dst 141 | } 142 | } 143 | } 144 | } 145 | func parseService(node *Node, result *parseResult) { 146 | if service, ok := services[node.Name]; ok { 147 | for _, relation := range node.DstNodes { 148 | dst := relation.Node.Name 149 | service.Refs = append(service.Refs, dst) 150 | } 151 | } 152 | } 153 | func fullNameCallback(fullName, methodName string) { 154 | tmp := strings.Split(fullName, "::") 155 | subDomain := tmp[0] 156 | if _, ok := subDomainMap[subDomain]; ok { 157 | subDomainMap[subDomain] = append(subDomainMap[subDomain], methodName) 158 | } 159 | } 160 | func parseCode(codeDotfile string) { 161 | node := ParseDoxygenFile(codeDotfile) 162 | node.RemoveNS(fullNameCallback) 163 | 164 | codeDotFileParseResult := &parseResult{ 165 | es: make(map[string]*Entity), 166 | vos: make(map[string]*ValueObject), 167 | } 168 | 169 | codeDotFileParseResult.parseNode(node, parseRelation). 170 | parseNode(node, parseAR). 171 | parseNode(node, parseEntity). 172 | parseNode(node, parseRepo) 173 | } 174 | 175 | func fullNameCallbackForLayer(fullName, methodName string) { 176 | tmp := strings.Split(fullName, "::") 177 | layer := tmp[0] 178 | if _, ok := layerMap[layer]; ok { 179 | layerMap[layer] = append(layerMap[layer], methodName) 180 | } 181 | } 182 | 183 | func parseCodeForSolution(codeDotfile string) { 184 | node := ParseDoxygenFile(codeDotfile) 185 | node.RemoveNS(fullNameCallbackForLayer) 186 | 187 | codeDotFileParseResult := &parseResult{ 188 | es: make(map[string]*Entity), 189 | vos: make(map[string]*ValueObject), 190 | } 191 | 192 | codeDotFileParseResult.parseNode(node, parseRelation). 193 | parseNode(node, parseAR). 194 | parseNode(node, parseEntity). 195 | parseNode(node, parseRepo). 196 | parseNode(node, parseService) 197 | } 198 | 199 | func ParseCodeProblemModel(codeDir string, subs []string) *ProblemModel { 200 | codeDotFiles := codeDotFiles(codeDir) 201 | codeArs = make(map[string]*Entity) 202 | repos = make(map[string]*Repository) 203 | providers = make(map[string]*Provider) 204 | subDomainMap = make(map[string][]string) 205 | for _, sub := range subs { 206 | subDomainMap[sub] = make([]string, 0) 207 | } 208 | for _, codeDotfile := range codeDotFiles { 209 | parseCode(codeDotfile) 210 | } 211 | 212 | subDomains := make(map[string]*SubDomain) 213 | if len(subDomainMap) > 0 { 214 | for key := range subDomainMap { 215 | t_ars := make(map[string]*Entity) 216 | t_repos := make(map[string]*Repository) 217 | t_providers := make(map[string]*Provider) 218 | for _, ekey := range subDomainMap[key] { 219 | if ar, ok := codeArs[ekey]; ok { 220 | 221 | t_ars[ekey] = ar 222 | } 223 | if repo, ok := repos[ekey]; ok { 224 | t_repos[ekey] = repo 225 | } 226 | if provider, ok := providers[ekey]; ok { 227 | t_providers[ekey] = provider 228 | } 229 | } 230 | subDomain := &SubDomain{ARs: t_ars, Repos: t_repos, Providers: t_providers} 231 | subDomains[key] = subDomain 232 | } 233 | } else { 234 | 235 | subDomain := &SubDomain{ARs: codeArs, Repos: repos, Providers: providers} 236 | subDomains["subdomain"] = subDomain 237 | } 238 | 239 | return &ProblemModel{SubDomains: subDomains} 240 | 241 | } 242 | 243 | func ParseCodeSolutionModel(codeDir string, layers []string) *BCModel { 244 | codeDotFiles := codeDotFiles(codeDir) 245 | codeArs = make(map[string]*Entity) 246 | repos = make(map[string]*Repository) 247 | services = make(map[string]*Service) 248 | providers = make(map[string]*Provider) 249 | layerMap = make(map[string][]string) 250 | for _, layer := range layers { 251 | layerMap[layer] = make([]string, 0) 252 | } 253 | for _, codeDotfile := range codeDotFiles { 254 | parseCodeForSolution(codeDotfile) 255 | } 256 | model := NewBCModel() 257 | for key := range layerMap { 258 | model.AppendLayer(key) 259 | 260 | for _, o := range layerMap[key] { 261 | if ar, ok := codeArs[o]; ok { 262 | model.AddARToLayer(key, ar) 263 | } 264 | if repo, ok := repos[o]; ok { 265 | model.AddRepoToLayer(key, repo) 266 | } 267 | if service, ok := services[o]; ok { 268 | model.AddServiceToLayer(key, service) 269 | } 270 | if provider, ok := providers[o]; ok && key == "services" { 271 | model.AddProviderToLayer(key, provider) 272 | } 273 | } 274 | } 275 | return model 276 | } 277 | -------------------------------------------------------------------------------- /tequila_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | . "github.com/newlee/tequila" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Tequila", func() { 10 | const subDomainName = "subdomain" 11 | const aggregateAName = "AggregateRootA" 12 | const aggregateBName = "AggregateRootB" 13 | var subs = make([]string, 0) 14 | Context("Parse DDD Model", func() { 15 | It("step1", func() { 16 | 17 | dotFile := "examples/step1-problem.dot" 18 | ars := ParseProblemModel(dotFile).SubDomains[subDomainName].ARs 19 | 20 | Expect(len(ars)).Should(Equal(1)) 21 | aggregateA := ars[aggregateAName] 22 | Expect(len(aggregateA.Entities)).Should(Equal(1)) 23 | Expect(len(aggregateA.VOs)).Should(Equal(1)) 24 | entityB := aggregateA.Entities[0] 25 | Expect(len(entityB.VOs)).Should(Equal(1)) 26 | }) 27 | It("step2", func() { 28 | 29 | dotFile := "examples/step2-problem.dot" 30 | ars := ParseProblemModel(dotFile).SubDomains[subDomainName].ARs 31 | 32 | Expect(len(ars)).Should(Equal(2)) 33 | aggregateA := ars[aggregateAName] 34 | Expect(len(aggregateA.Entities)).Should(Equal(1)) 35 | Expect(len(aggregateA.VOs)).Should(Equal(1)) 36 | entityB := aggregateA.Entities[0] 37 | Expect(len(entityB.VOs)).Should(Equal(1)) 38 | 39 | aggregateB := ars[aggregateBName] 40 | Expect(len(aggregateB.Entities)).Should(Equal(0)) 41 | Expect(len(aggregateB.Refs)).Should(Equal(1)) 42 | Expect(aggregateB.Refs[0]).Should(Equal(aggregateA)) 43 | }) 44 | It("step2 with repository", func() { 45 | 46 | dotFile := "examples/step2-problem.dot" 47 | model := ParseProblemModel(dotFile) 48 | repos := model.SubDomains[subDomainName].Repos 49 | 50 | Expect(len(repos)).Should(Equal(1)) 51 | Expect(repos["AggregateRootARepo"].For).Should(Equal(aggregateAName)) 52 | }) 53 | It("step2 with provider interface", func() { 54 | 55 | dotFile := "examples/step2-problem.dot" 56 | model := ParseProblemModel(dotFile) 57 | providers := model.SubDomains[subDomainName].Providers 58 | 59 | Expect(len(providers)).Should(Equal(1)) 60 | }) 61 | 62 | It("sub domain", func() { 63 | dotFile := "examples/subdomain.dot" 64 | model := ParseProblemModel(dotFile) 65 | 66 | Expect(len(model.SubDomains)).Should(Equal(2)) 67 | subDomain := model.SubDomains["subdomain1"] 68 | ars := subDomain.ARs 69 | aggregateA := ars[aggregateAName] 70 | Expect(len(aggregateA.Entities)).Should(Equal(1)) 71 | Expect(len(aggregateA.VOs)).Should(Equal(1)) 72 | entityB := aggregateA.Entities[0] 73 | Expect(len(entityB.VOs)).Should(Equal(1)) 74 | 75 | aggregateB := ars[aggregateBName] 76 | Expect(len(aggregateB.Entities)).Should(Equal(0)) 77 | Expect(len(aggregateB.Refs)).Should(Equal(1)) 78 | Expect(aggregateB.Refs[0]).Should(Equal(aggregateA)) 79 | 80 | subDomain = model.SubDomains["subdomain2"] 81 | ars = subDomain.ARs 82 | aggregateC := ars["AggregateRootC"] 83 | Expect(len(aggregateC.Entities)).Should(Equal(1)) 84 | Expect(len(aggregateC.VOs)).Should(Equal(0)) 85 | EntityC := aggregateC.Entities[0] 86 | Expect(len(EntityC.VOs)).Should(Equal(0)) 87 | 88 | }) 89 | }) 90 | 91 | Context("Parse Doxygen dot files", func() { 92 | 93 | It("step1", func() { 94 | 95 | codeDir := "examples/step1-code/html" 96 | codeArs := ParseCodeProblemModel(codeDir, subs).SubDomains[subDomainName].ARs 97 | 98 | Expect(len(codeArs)).Should(Equal(1)) 99 | Expect(len(codeArs[aggregateAName].Entities)).Should(Equal(1)) 100 | Expect(len(codeArs[aggregateAName].VOs)).Should(Equal(1)) 101 | entityB := codeArs[aggregateAName].Entities[0] 102 | Expect(len(entityB.VOs)).Should(Equal(1)) 103 | }) 104 | It("step2", func() { 105 | 106 | codeDir := "examples/step2-code/html" 107 | codeArs := ParseCodeProblemModel(codeDir, subs).SubDomains[subDomainName].ARs 108 | 109 | Expect(len(codeArs)).Should(Equal(2)) 110 | ara := aggregateAName 111 | arb := aggregateBName 112 | 113 | Expect(len(codeArs[ara].Entities)).Should(Equal(1)) 114 | Expect(len(codeArs[ara].VOs)).Should(Equal(1)) 115 | entityB := codeArs[ara].Entities[0] 116 | Expect(len(entityB.VOs)).Should(Equal(1)) 117 | 118 | Expect(len(codeArs[arb].Entities)).Should(Equal(0)) 119 | Expect(len(codeArs[arb].Refs)).Should(Equal(1)) 120 | Expect(codeArs[arb].Refs[0]).Should(Equal(codeArs[ara])) 121 | }) 122 | 123 | It("step2 with repository", func() { 124 | 125 | codeDir := "examples/step2-code/html" 126 | model := ParseCodeProblemModel(codeDir, subs) 127 | repos := model.SubDomains[subDomainName].Repos 128 | 129 | Expect(len(repos)).Should(Equal(1)) 130 | Expect(repos["AggregateRootARepo"].For).Should(Equal(aggregateAName)) 131 | }) 132 | 133 | It("step2 with provider interface", func() { 134 | codeDir := "examples/step2-code/html" 135 | model := ParseCodeProblemModel(codeDir, subs) 136 | providers := model.SubDomains[subDomainName].Providers 137 | 138 | Expect(len(providers)).Should(Equal(1)) 139 | }) 140 | It("step3 should failded when aggregate ref another entity", func() { 141 | codeDir := "examples/step2-code/html" 142 | model := ParseCodeProblemModel(codeDir, subs) 143 | 144 | Expect(model.Validate()).Should(Equal(true)) 145 | 146 | codeDir = "examples/step3-code/html" 147 | model = ParseCodeProblemModel(codeDir, subs) 148 | 149 | Expect(model.Validate()).Should(Equal(false)) 150 | }) 151 | }) 152 | 153 | Context("Parse Doxygen dot files with java", func() { 154 | 155 | It("step2", func() { 156 | 157 | codeDir := "examples/step2-Java/html" 158 | codeArs := ParseCodeProblemModel(codeDir, subs).SubDomains[subDomainName].ARs 159 | 160 | Expect(len(codeArs)).Should(Equal(2)) 161 | ara := aggregateAName 162 | arb := aggregateBName 163 | 164 | Expect(len(codeArs[ara].Entities)).Should(Equal(1)) 165 | Expect(len(codeArs[ara].VOs)).Should(Equal(1)) 166 | entityB := codeArs[ara].Entities[0] 167 | Expect(len(entityB.VOs)).Should(Equal(1)) 168 | 169 | Expect(len(codeArs[arb].Entities)).Should(Equal(0)) 170 | Expect(len(codeArs[arb].Refs)).Should(Equal(1)) 171 | Expect(codeArs[arb].Refs[0]).Should(Equal(codeArs[ara])) 172 | }) 173 | 174 | It("step2 with repository", func() { 175 | 176 | codeDir := "examples/step2-Java/html" 177 | model := ParseCodeProblemModel(codeDir, subs) 178 | repos := model.SubDomains[subDomainName].Repos 179 | 180 | Expect(len(repos)).Should(Equal(1)) 181 | Expect(repos["AggregateRootARepo"].For).Should(Equal(aggregateAName)) 182 | }) 183 | 184 | It("step2 with provider interface", func() { 185 | codeDir := "examples/step2-Java/html" 186 | model := ParseCodeProblemModel(codeDir, subs) 187 | providers := model.SubDomains[subDomainName].Providers 188 | 189 | Expect(len(providers)).Should(Equal(1)) 190 | }) 191 | It("step3 should failded when aggregate ref another entity", func() { 192 | codeDir := "examples/step2-Java/html" 193 | model := ParseCodeProblemModel(codeDir, subs) 194 | 195 | Expect(model.Validate()).Should(Equal(true)) 196 | }) 197 | 198 | It("sub domain", func() { 199 | codeDir := "examples/subdomain-code/html" 200 | model := ParseCodeProblemModel(codeDir, []string{"subdomain1", "subdomain2"}) 201 | 202 | Expect(len(model.SubDomains)).Should(Equal(2)) 203 | subDomain := model.SubDomains["subdomain1"] 204 | ars := subDomain.ARs 205 | aggregateA := ars[aggregateAName] 206 | Expect(len(aggregateA.Entities)).Should(Equal(1)) 207 | Expect(len(aggregateA.VOs)).Should(Equal(1)) 208 | entityB := aggregateA.Entities[0] 209 | Expect(len(entityB.VOs)).Should(Equal(1)) 210 | 211 | aggregateB := ars[aggregateBName] 212 | Expect(len(aggregateB.Entities)).Should(Equal(0)) 213 | Expect(len(aggregateB.Refs)).Should(Equal(1)) 214 | Expect(aggregateB.Refs[0]).Should(Equal(aggregateA)) 215 | 216 | subDomain = model.SubDomains["subdomain2"] 217 | ars = subDomain.ARs 218 | aggregateC := ars["AggregateRootC"] 219 | Expect(len(aggregateC.Entities)).Should(Equal(1)) 220 | Expect(len(aggregateC.VOs)).Should(Equal(0)) 221 | EntityC := aggregateC.Entities[0] 222 | Expect(len(EntityC.VOs)).Should(Equal(0)) 223 | 224 | }) 225 | }) 226 | 227 | Context("Compare when have inheritance tree", func() { 228 | 229 | It("inheritance tree", func() { 230 | codeDir := "examples/inheritance-tree-code/html" 231 | codeArs := ParseCodeProblemModel(codeDir, subs).SubDomains[subDomainName].ARs 232 | 233 | Expect(len(codeArs)).Should(Equal(2)) 234 | Expect(len(codeArs[aggregateAName].Entities)).Should(Equal(2)) 235 | Expect(len(codeArs[aggregateAName].VOs)).Should(Equal(1)) 236 | entityB := codeArs[aggregateAName].Entities[0] 237 | entityC := codeArs[aggregateAName].Entities[1] 238 | Expect(len(entityB.VOs) + len(entityC.VOs)).Should(Equal(1)) 239 | 240 | }) 241 | }) 242 | }) 243 | -------------------------------------------------------------------------------- /viz/tq_viz_cli/cmd/sql_parse.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/spf13/cobra" 7 | "github.com/xwb1989/sqlparser" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/newlee/tequila/viz" 14 | "reflect" 15 | "regexp" 16 | ) 17 | 18 | func printJoin(table sqlparser.TableExpr, currentQuery *viz.Query) { 19 | switch table := table.(type) { 20 | case *sqlparser.JoinTableExpr: 21 | printJoin(table.RightExpr, currentQuery) 22 | printJoin(table.LeftExpr, currentQuery) 23 | printWhen(table.Condition.On, currentQuery) 24 | case *sqlparser.AliasedTableExpr: 25 | tName := sqlparser.String(table.Expr) 26 | if strings.HasPrefix(tName, "ODSUSER.T_") { 27 | currentQuery.AddTable(tName, table.As.String()) 28 | } 29 | 30 | if strings.HasPrefix(tName, "(select") && strings.Contains(tName, "ODSUSER.") { 31 | tName = strings.Trim(tName, " \n") 32 | parseQuery(tName[1 : len(tName)-1]) 33 | } 34 | default: 35 | fmt.Println("----------------") 36 | } 37 | } 38 | func printWhen(when sqlparser.Expr, currentQuery *viz.Query) { 39 | switch when := when.(type) { 40 | case *sqlparser.ComparisonExpr: 41 | left := sqlparser.String(when.Left) 42 | if strings.Contains(left, ".") { 43 | currentQuery.AddColumn(left) 44 | } 45 | right := sqlparser.String(when.Right) 46 | if strings.Contains(right, ".") { 47 | currentQuery.AddColumn(right) 48 | } 49 | 50 | switch l := when.Left.(type) { 51 | case *sqlparser.Subquery: 52 | parseQuery(sqlparser.String(l.Select)) 53 | default: 54 | printWhen(l, currentQuery) 55 | } 56 | switch l := when.Right.(type) { 57 | case *sqlparser.Subquery: 58 | parseQuery(sqlparser.String(l.Select)) 59 | default: 60 | printWhen(l, currentQuery) 61 | } 62 | case *sqlparser.OrExpr: 63 | printWhen(when.Left, currentQuery) 64 | printWhen(when.Right, currentQuery) 65 | case *sqlparser.ExistsExpr: 66 | parseQuery(sqlparser.String(when.Subquery.Select)) 67 | case *sqlparser.IsExpr: 68 | currentQuery.AddColumn(sqlparser.String(when.Expr)) 69 | case *sqlparser.ParenExpr: 70 | printWhen(when.Expr, currentQuery) 71 | case *sqlparser.AndExpr: 72 | printWhen(when.Left, currentQuery) 73 | printWhen(when.Right, currentQuery) 74 | case *sqlparser.ColName: 75 | tname := when.Qualifier.Name.String() 76 | col := when.Name.String() 77 | if tname != "" { 78 | currentQuery.AddColumn(fmt.Sprintf("%s.%s", tname, col)) 79 | } 80 | case *sqlparser.SubstrExpr: 81 | tname := when.Name.Qualifier.Name.String() 82 | col := when.Name.Name.String() 83 | if tname != "" { 84 | currentQuery.AddColumn(fmt.Sprintf("%s.%s", tname, col)) 85 | } 86 | case *sqlparser.FuncExpr: 87 | tname := when.Qualifier.String() 88 | col := when.Name.String() 89 | if tname != "" { 90 | currentQuery.AddColumn(fmt.Sprintf("%s.%s", tname, col)) 91 | } 92 | case *sqlparser.NotExpr: 93 | printWhen(when.Expr, currentQuery) 94 | case *sqlparser.SQLVal: 95 | case *sqlparser.BinaryExpr: 96 | case sqlparser.ValTuple: 97 | case *sqlparser.UnaryExpr: 98 | 99 | default: 100 | fmt.Println(reflect.TypeOf(when).String() + " --- " + sqlparser.String(when)) 101 | } 102 | } 103 | 104 | var queryArr = make([]*viz.Query, 0) 105 | 106 | var sqlParseCmd *cobra.Command = &cobra.Command{ 107 | Use: "sp", 108 | Short: "query sql parse", 109 | Long: ``, 110 | Run: func(cmd *cobra.Command, args []string) { 111 | source := cmd.Flag("source").Value.String() 112 | codeFiles := make([]string, 0) 113 | filepath.Walk(source, func(path string, fi os.FileInfo, err error) error { 114 | if strings.HasSuffix(path, ".bdy") || strings.HasSuffix(path, ".sql") { 115 | codeFiles = append(codeFiles, path) 116 | } 117 | 118 | return nil 119 | }) 120 | 121 | querys := make([]string, 0) 122 | for _, codeFileName := range codeFiles { 123 | codeFile, _ := os.Open(codeFileName) 124 | scanner := bufio.NewScanner(codeFile) 125 | scanner.Split(bufio.ScanLines) 126 | query := "" 127 | begin := false 128 | for scanner.Scan() { 129 | 130 | line := scanner.Text() 131 | 132 | tmp := strings.Fields(strings.ToUpper(line)) 133 | for _, word := range tmp { 134 | 135 | if word == "UPDATE" { 136 | begin = true 137 | } 138 | if !begin && strings.HasSuffix(word, "SELECT") { 139 | begin = true 140 | } 141 | 142 | if word != "" { 143 | break 144 | } 145 | } 146 | 147 | if begin { 148 | if strings.Contains(line, "--") && !strings.Contains(line, "/") { 149 | tmp := strings.Split(line, "--") 150 | line = tmp[0] 151 | } 152 | if strings.Contains(line, "F_QHH(") && strings.Contains(line, "SUBSTR") { 153 | re, _ := regexp.Compile("F_QHH\\(([\\S\\s]+?)\\)") 154 | submatch := re.FindStringSubmatch(line) 155 | 156 | line = strings.Replace(line, submatch[0], submatch[1], -1) 157 | } 158 | 159 | if strings.Contains(line, "TRIM(") && strings.Contains(line, "SUBSTR") { 160 | re, _ := regexp.Compile("TRIM\\(([\\S\\s]+?)\\)") 161 | submatch := re.FindStringSubmatch(line) 162 | 163 | line = strings.Replace(line, submatch[0], submatch[1], -1) 164 | } 165 | 166 | if strings.Contains(line, "ROW_NUMBER()") { 167 | line = "0" 168 | } 169 | 170 | if strings.Contains(line, "LENGTHB(") { 171 | line = strings.Replace(line, "LENGTHB(", "LENGTH(", -1) 172 | } 173 | 174 | if strings.Contains(strings.ToUpper(line), " DATE") && !strings.Contains(line, "SELECT") { 175 | line = strings.Replace(strings.ToUpper(line), " DATE", "", -1) 176 | } 177 | 178 | if strings.HasPrefix(line, "/*") { 179 | continue 180 | } 181 | 182 | query = query + line + "\n" 183 | } 184 | if strings.Contains(line, ";") && begin { 185 | begin = false 186 | querys = append(querys, query) 187 | query = "" 188 | } 189 | } 190 | } 191 | 192 | for _, query := range querys { 193 | query = strings.Trim(query, " ") 194 | if strings.HasPrefix(query, "(") { 195 | query = query[1 : len(query)-3] 196 | } 197 | parseQuery(query) 198 | } 199 | fq := &viz.Query{Sql: "", Tables: make(map[string]*viz.QueryTable)} 200 | for _, q := range queryArr { 201 | fq.Merge(q) 202 | } 203 | fq.ToString() 204 | 205 | }, 206 | } 207 | var isRetry = false 208 | 209 | func parseQuery(query string) { 210 | if !strings.Contains(strings.ToUpper(query), "ODSUSER.") { 211 | return 212 | } 213 | if strings.HasSuffix(query, "*/\n") { 214 | return 215 | } 216 | sql := strings.ToUpper(query) 217 | sql = strings.Replace(query, "(+)", "", -1) 218 | sql = strings.Replace(sql, ";", "", -1) 219 | sql = strings.Replace(sql, "INTO", "", -1) 220 | 221 | stmt, err := sqlparser.Parse(sql) 222 | if err != nil { 223 | if !isRetry { 224 | isRetry = true 225 | parseQuery(query + " AS T") 226 | isRetry = false 227 | } 228 | //if isRetry { 229 | // fmt.Println(sql) 230 | // fmt.Println("parse error: " + err.Error()) 231 | //} 232 | return 233 | } 234 | switch stmt := stmt.(type) { 235 | case *sqlparser.Select: 236 | var currentQuery = viz.NewQuery(query) 237 | 238 | for _, node := range stmt.From { 239 | printJoin(node, currentQuery) 240 | } 241 | for _, node := range stmt.SelectExprs { 242 | switch node := node.(type) { 243 | case *sqlparser.AliasedExpr: 244 | switch expr := node.Expr.(type) { 245 | case *sqlparser.CaseExpr: 246 | for _, when := range expr.Whens { 247 | printWhen(when.Cond, currentQuery) 248 | } 249 | default: 250 | column := sqlparser.String(expr) 251 | column = strings.ToUpper(column) 252 | if column != "NULL" && column != "SYSDATE" && !strings.HasPrefix(column, "'") && !strings.HasPrefix(column, "V_") { 253 | _, err := strconv.Atoi(column) 254 | if err != nil { 255 | currentQuery.AddColumn(column) 256 | } 257 | } 258 | } 259 | 260 | default: 261 | currentQuery.AddColumn(sqlparser.String(node)) 262 | } 263 | 264 | } 265 | if stmt.Where != nil { 266 | printWhen(stmt.Where.Expr, currentQuery) 267 | } 268 | queryArr = append(queryArr, currentQuery) 269 | case *sqlparser.Update: 270 | 271 | var currentQuery = viz.NewQuery(query) 272 | for _, node := range stmt.Exprs { 273 | switch node := node.Expr.(type) { 274 | case *sqlparser.Subquery: 275 | parseQuery(sqlparser.String(node.Select)) 276 | default: 277 | } 278 | } 279 | printWhen(stmt.Where.Expr, currentQuery) 280 | case *sqlparser.Delete: 281 | var currentQuery = viz.NewQuery(query) 282 | printWhen(stmt.Where.Expr, currentQuery) 283 | case *sqlparser.Insert: 284 | } 285 | } 286 | 287 | func init() { 288 | rootCmd.AddCommand(sqlParseCmd) 289 | 290 | sqlParseCmd.Flags().StringP("source", "s", "", "source code directory") 291 | sqlParseCmd.Flags().StringP("filter", "f", "coll__graph.dot", "dot file filter") 292 | sqlParseCmd.Flags().StringP("output", "o", "dep.dot", "output dot file name") 293 | } 294 | -------------------------------------------------------------------------------- /viz/incl_viz.go: -------------------------------------------------------------------------------- 1 | package viz 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/awalterschulze/gographviz" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type Relation struct { 16 | From string 17 | To string 18 | Style string 19 | } 20 | 21 | type FullGraph struct { 22 | NodeList map[string]string 23 | RelationList map[string]*Relation 24 | } 25 | 26 | type Fan struct { 27 | Name string 28 | FanIn int 29 | FanOut int 30 | } 31 | 32 | func (f *FullGraph) FindCrossRef(merge func(string) string) []string { 33 | mergedRelationMap := make(map[string]string) 34 | result := make([]string, 0) 35 | for key := range f.RelationList { 36 | relation := f.RelationList[key] 37 | mergedFrom := merge(relation.From) 38 | mergedTo := merge(relation.To) 39 | if mergedFrom == mergedTo { 40 | continue 41 | } 42 | if _, ok := mergedRelationMap[mergedTo+mergedFrom]; ok { 43 | result = append(result, mergedFrom+" <-> "+mergedTo) 44 | } 45 | mergedRelationMap[mergedFrom+mergedTo] = "" 46 | } 47 | return result 48 | } 49 | 50 | func (f *FullGraph) MergeHeaderFile(merge func(string) string) *FullGraph { 51 | result := &FullGraph{ 52 | NodeList: make(map[string]string), 53 | RelationList: make(map[string]*Relation), 54 | } 55 | nodes := make(map[string]string) 56 | 57 | for key := range f.NodeList { 58 | mergedKey := merge(key) 59 | nodes[key] = mergedKey 60 | result.NodeList[mergedKey] = mergedKey 61 | } 62 | for key := range f.RelationList { 63 | relation := f.RelationList[key] 64 | mergedFrom := merge(relation.From) 65 | mergedTo := merge(relation.To) 66 | if mergedFrom == mergedTo { 67 | continue 68 | } 69 | 70 | mergedRelation := &Relation{ 71 | From: mergedFrom, 72 | To: mergedTo, 73 | Style: "\"solid\"", 74 | } 75 | 76 | result.RelationList[mergedRelation.From+mergedRelation.To] = mergedRelation 77 | } 78 | return result 79 | } 80 | 81 | func (f *FullGraph) EntryPoints(merge func(string) string) []string { 82 | mergedGraph := f.MergeHeaderFile(merge) 83 | fromMap := make(map[string]bool) 84 | toMap := make(map[string]bool) 85 | for key := range mergedGraph.RelationList { 86 | relation := mergedGraph.RelationList[key] 87 | if relation.From == "main" { 88 | continue 89 | } 90 | fromMap[relation.From] = true 91 | toMap[relation.To] = true 92 | } 93 | result := make([]string, 0) 94 | for key := range fromMap { 95 | if _, ok := toMap[key]; !ok { 96 | result = append(result, key) 97 | } 98 | } 99 | return result 100 | } 101 | 102 | func (f *FullGraph) SortedByFan(merge func(string) string) []*Fan { 103 | mergedGraph := f.MergeHeaderFile(merge) 104 | result := make([]*Fan, len(mergedGraph.NodeList)) 105 | index := 0 106 | fanMap := make(map[string]*Fan) 107 | for key := range mergedGraph.NodeList { 108 | fan := &Fan{Name: key} 109 | result[index] = fan 110 | fanMap[key] = fan 111 | index++ 112 | } 113 | for key := range mergedGraph.RelationList { 114 | relation := mergedGraph.RelationList[key] 115 | fanMap[relation.From].FanOut++ 116 | fanMap[relation.To].FanIn++ 117 | } 118 | sort.Slice(result, func(i, j int) bool { 119 | return (result[i].FanIn + result[i].FanOut) > (result[j].FanIn + result[j].FanOut) 120 | }) 121 | return result 122 | } 123 | 124 | var fullGraph *FullGraph 125 | 126 | func parseRelation(edge *gographviz.Edge, nodes map[string]string) { 127 | if _, ok := nodes[edge.Src]; ok { 128 | if _, ok := nodes[edge.Dst]; ok { 129 | dst := nodes[edge.Dst] 130 | src := nodes[edge.Src] 131 | dst = strings.ToLower(dst) 132 | src = strings.ToLower(src) 133 | relation := &Relation{ 134 | From: dst, 135 | To: src, 136 | Style: "\"solid\"", 137 | } 138 | fullGraph.RelationList[relation.From+"->"+relation.To] = relation 139 | } 140 | } 141 | } 142 | 143 | func filterDirectory(fullMethodName string) bool { 144 | if strings.Contains(fullMethodName, "_test") { 145 | return true 146 | } 147 | 148 | if strings.Contains(fullMethodName, "Test") { 149 | return true 150 | } 151 | 152 | if strings.Contains(fullMethodName, "/Library/") { 153 | return true 154 | } 155 | return false 156 | } 157 | 158 | func parseDotFile(codeDotfile string) { 159 | fbuf, _ := ioutil.ReadFile(codeDotfile) 160 | parseFromBuffer(fbuf) 161 | } 162 | func parseFromBuffer(fbuf []byte) { 163 | g, err := gographviz.Read(fbuf) 164 | if err != nil { 165 | fmt.Println(string(fbuf)) 166 | } 167 | nodes := make(map[string]string) 168 | for _, node := range g.Nodes.Nodes { 169 | fullMethodName := strings.Replace(node.Attrs["label"], "\"", "", 2) 170 | if strings.Contains(fullMethodName, " ") { 171 | tmp := strings.Split(fullMethodName, " ") 172 | fullMethodName = tmp[len(tmp)-1] 173 | } 174 | if filterDirectory(fullMethodName) { 175 | continue 176 | } 177 | 178 | methodName := formatMethodName(fullMethodName) 179 | fullGraph.NodeList[methodName] = methodName 180 | nodes[node.Name] = methodName 181 | } 182 | for key := range g.Edges.DstToSrcs { 183 | for edgesKey := range g.Edges.DstToSrcs[key] { 184 | for _, edge := range g.Edges.DstToSrcs[key][edgesKey] { 185 | parseRelation(edge, nodes) 186 | } 187 | } 188 | } 189 | } 190 | func formatMethodName(fullMethodName string) string { 191 | methodName := strings.Replace(fullMethodName, "\\l", "", -1) 192 | methodName = strings.Replace(methodName, "src/", "", -1) 193 | methodName = strings.Replace(methodName, "include/", "", -1) 194 | methodName = strings.ToLower(methodName) 195 | return methodName 196 | } 197 | 198 | func codeDotFiles(codeDir string, fileFilter func(string) bool) []string { 199 | codeDotFiles := make([]string, 0) 200 | filepath.Walk(codeDir, func(path string, fi os.FileInfo, err error) error { 201 | if strings.HasSuffix(path, ".dot") { 202 | if fileFilter(path) { 203 | //return nil 204 | if strings.Contains(path, "_test_") { 205 | return nil 206 | } 207 | 208 | codeDotFiles = append(codeDotFiles, path) 209 | } 210 | } 211 | 212 | return nil 213 | }) 214 | 215 | return codeDotFiles 216 | } 217 | 218 | func ParseInclude(codeDir string) *FullGraph { 219 | fullGraph = &FullGraph{ 220 | NodeList: make(map[string]string), 221 | RelationList: make(map[string]*Relation), 222 | } 223 | codeDotFiles := codeDotFiles(codeDir, func(path string) bool { 224 | 225 | return strings.HasSuffix(path, "_dep__incl.dot") 226 | }) 227 | 228 | for _, codeDotfile := range codeDotFiles { 229 | parseDotFile(codeDotfile) 230 | } 231 | 232 | return fullGraph 233 | } 234 | 235 | func (fullGraph *FullGraph) ToDot(fileName string, split string, filter func(string) bool) { 236 | graph := gographviz.NewGraph() 237 | graph.SetName("G") 238 | 239 | nodeIndex := 1 240 | layerIndex := 1 241 | nodes := make(map[string]string) 242 | 243 | layerMap := make(map[string][]string) 244 | 245 | for nodeKey := range fullGraph.NodeList { 246 | if filter(nodeKey) { 247 | continue 248 | } 249 | 250 | tmp := strings.Split(nodeKey, split) 251 | packageName := tmp[0] 252 | if packageName == nodeKey { 253 | packageName = "main" 254 | } 255 | if len(tmp) > 2 { 256 | packageName = strings.Join(tmp[0:len(tmp)-1], split) 257 | } 258 | 259 | if _, ok := layerMap[packageName]; !ok { 260 | layerMap[packageName] = make([]string, 0) 261 | } 262 | layerMap[packageName] = append(layerMap[packageName], nodeKey) 263 | } 264 | 265 | for layer := range layerMap { 266 | layerAttr := make(map[string]string) 267 | layerAttr["label"] = "\"" + layer + "\"" 268 | layerName := "cluster" + strconv.Itoa(layerIndex) 269 | graph.AddSubGraph("G", layerName, layerAttr) 270 | layerIndex++ 271 | for _, node := range layerMap[layer] { 272 | attrs := make(map[string]string) 273 | fileName := strings.Replace(node, layer+split, "", -1) 274 | attrs["label"] = "\"" + fileName + "\"" 275 | attrs["shape"] = "box" 276 | graph.AddNode(layerName, "node"+strconv.Itoa(nodeIndex), attrs) 277 | nodes[node] = "node" + strconv.Itoa(nodeIndex) 278 | nodeIndex++ 279 | } 280 | } 281 | 282 | for key := range fullGraph.RelationList { 283 | relation := fullGraph.RelationList[key] 284 | if nodes[relation.From] != "" && nodes[relation.To] != "" { 285 | fromNode := nodes[relation.From] 286 | toNode := nodes[relation.To] 287 | attrs := make(map[string]string) 288 | attrs["style"] = relation.Style 289 | graph.AddEdge(fromNode, toNode, true, attrs) 290 | } 291 | } 292 | 293 | f, _ := os.Create(fileName) 294 | w := bufio.NewWriter(f) 295 | w.WriteString("di" + graph.String()) 296 | w.Flush() 297 | } 298 | 299 | var Foo = func() string { 300 | return "" 301 | } 302 | 303 | func (fullGraph *FullGraph) ToDataSet(fileName string, split string, filter func(string) bool) { 304 | nodes := make(map[string]string) 305 | 306 | for nodeKey := range fullGraph.NodeList { 307 | if filter(nodeKey) { 308 | continue 309 | } 310 | 311 | nodes[nodeKey] = nodeKey 312 | } 313 | 314 | relMap := make(map[string][]string) 315 | for key := range fullGraph.RelationList { 316 | relation := fullGraph.RelationList[key] 317 | 318 | if nodes[relation.From] == "" && nodes[relation.To] != "" { 319 | if _, ok := relMap[relation.From]; !ok { 320 | relMap[relation.From] = make([]string, 0) 321 | } 322 | relMap[relation.From] = append(relMap[relation.From], relation.To) 323 | } 324 | } 325 | 326 | for key := range relMap { 327 | tos := relMap[key] 328 | fmt.Print("['" + strings.Join(tos, "','") + "'],") 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /model/solution.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type layer interface { 8 | Add(name, comment string) 9 | getNodes() []string 10 | addRelations(src string, dsts []string) 11 | getRelations() map[string][]string 12 | compare(other interface{}) bool 13 | } 14 | 15 | type Layer struct { 16 | Name string 17 | nodes map[string]string 18 | layer layer 19 | } 20 | 21 | func (layer *Layer) GetNodes() []string { 22 | return layer.layer.getNodes() 23 | } 24 | 25 | func (layer *Layer) GetRelations() map[string][]string { 26 | return layer.layer.getRelations() 27 | } 28 | 29 | type BCModel struct { 30 | Layers map[string]*Layer 31 | } 32 | 33 | type Service struct { 34 | name string 35 | Refs []string 36 | } 37 | 38 | func NewService(name string) *Service { 39 | return &Service{name: name} 40 | } 41 | 42 | type Api struct { 43 | name string 44 | Refs []string 45 | } 46 | 47 | type GateWay struct { 48 | name string 49 | Implement string 50 | } 51 | type DomainLayer struct { 52 | ARs map[string]*Entity 53 | es map[string]*Entity 54 | vos map[string]*ValueObject 55 | } 56 | 57 | type RepoLayer struct { 58 | Repos map[string]*Repository 59 | } 60 | 61 | type ServiceLayer struct { 62 | Providers map[string]*Provider 63 | Services map[string]*Service 64 | } 65 | 66 | type ApiLayer struct { 67 | Apis map[string]*Api 68 | } 69 | 70 | type GatewayLayer struct { 71 | GateWays map[string]*GateWay 72 | } 73 | 74 | func NewBCModel() *BCModel { 75 | return &BCModel{Layers: make(map[string]*Layer)} 76 | } 77 | 78 | func (layer *DomainLayer) Add(name, comment string) { 79 | if comment == "AR" { 80 | layer.ARs[name] = NewEntity(name) 81 | } 82 | 83 | if comment == "E" { 84 | layer.es[name] = NewEntity(name) 85 | } 86 | 87 | if comment == "VO" { 88 | layer.vos[name] = NewValueObject(name) 89 | } 90 | } 91 | 92 | func (layer *RepoLayer) Add(name, comment string) { 93 | if comment == "Repo" { 94 | layer.Repos[name] = NewRepository(name) 95 | } 96 | } 97 | 98 | func (layer *GatewayLayer) Add(name, comment string) { 99 | if comment == "Provider" { 100 | layer.GateWays[name] = &GateWay{name: name} 101 | } 102 | } 103 | 104 | func (layer *ServiceLayer) Add(name, comment string) { 105 | if comment == "Service" { 106 | layer.Services[name] = &Service{name: name} 107 | } 108 | if comment == "Provider" { 109 | layer.Providers[name] = &Provider{name: name} 110 | } 111 | } 112 | 113 | func (layer *ApiLayer) Add(name, comment string) { 114 | if comment == "Api" { 115 | layer.Apis[name] = &Api{name: name} 116 | } 117 | } 118 | 119 | func (layer *DomainLayer) getNodes() []string { 120 | result := make([]string, 0) 121 | for key := range layer.ARs { 122 | result = append(result, layer.ARs[key].name) 123 | } 124 | for key := range layer.es { 125 | result = append(result, layer.es[key].name) 126 | } 127 | 128 | for key := range layer.vos { 129 | result = append(result, layer.vos[key].name) 130 | } 131 | return result 132 | } 133 | 134 | func (layer *ServiceLayer) getNodes() []string { 135 | result := make([]string, 0) 136 | for key := range layer.Services { 137 | result = append(result, layer.Services[key].name) 138 | } 139 | for key := range layer.Providers { 140 | result = append(result, layer.Providers[key].name) 141 | } 142 | return result 143 | } 144 | 145 | func (layer *RepoLayer) getNodes() []string { 146 | result := make([]string, 0) 147 | for key := range layer.Repos { 148 | result = append(result, layer.Repos[key].name) 149 | } 150 | return result 151 | } 152 | 153 | func (layer *GatewayLayer) getNodes() []string { 154 | result := make([]string, 0) 155 | return result 156 | } 157 | 158 | func (layer *ApiLayer) getNodes() []string { 159 | result := make([]string, 0) 160 | return result 161 | } 162 | 163 | func (layer *DomainLayer) getRelations() map[string][]string { 164 | result := make(map[string][]string) 165 | for key := range layer.ARs { 166 | ar := layer.ARs[key] 167 | result[ar.name] = make([]string, 0) 168 | for _, entity := range ar.Entities { 169 | result[ar.name] = append(result[ar.name], entity.name) 170 | } 171 | for _, vo := range ar.VOs { 172 | result[ar.name] = append(result[ar.name], vo.name) 173 | } 174 | } 175 | return result 176 | } 177 | func (layer *RepoLayer) getRelations() map[string][]string { 178 | result := make(map[string][]string) 179 | for key := range layer.Repos { 180 | repo := layer.Repos[key] 181 | result[repo.name] = make([]string, 0) 182 | result[repo.name] = append(result[repo.name], repo.For) 183 | } 184 | return result 185 | } 186 | func (layer *ServiceLayer) getRelations() map[string][]string { 187 | result := make(map[string][]string) 188 | for key := range layer.Services { 189 | service := layer.Services[key] 190 | result[service.name] = service.Refs 191 | } 192 | 193 | return result 194 | } 195 | func (layer *ApiLayer) getRelations() map[string][]string { 196 | result := make(map[string][]string) 197 | 198 | return result 199 | } 200 | func (layer *GatewayLayer) getRelations() map[string][]string { 201 | result := make(map[string][]string) 202 | 203 | return result 204 | } 205 | func newLayer(name string) layer { 206 | if name == "domain" { 207 | return &DomainLayer{ARs: make(map[string]*Entity), 208 | es: make(map[string]*Entity), 209 | vos: make(map[string]*ValueObject)} 210 | } 211 | if name == "repositories" { 212 | return &RepoLayer{Repos: make(map[string]*Repository)} 213 | } 214 | 215 | if name == "gateways" { 216 | return &GatewayLayer{GateWays: make(map[string]*GateWay)} 217 | } 218 | 219 | if name == "services" { 220 | return &ServiceLayer{Services: make(map[string]*Service), Providers: make(map[string]*Provider)} 221 | } 222 | 223 | if name == "api" { 224 | return &ApiLayer{Apis: make(map[string]*Api)} 225 | } 226 | 227 | return nil 228 | } 229 | 230 | func (model *BCModel) AppendLayer(name string) { 231 | if _, ok := model.Layers[name]; !ok { 232 | model.Layers[name] = &Layer{Name: name, nodes: make(map[string]string), layer: newLayer(name)} 233 | } 234 | } 235 | 236 | func (model *BCModel) AppendNode(layerName, nodeName string) { 237 | model.Layers[layerName].nodes[nodeName] = layerName 238 | } 239 | 240 | func (model *BCModel) findLayer(nodeName string) *Layer { 241 | for key := range model.Layers { 242 | layer := model.Layers[key] 243 | if _, ok := layer.nodes[nodeName]; ok { 244 | return layer 245 | } 246 | } 247 | return nil 248 | } 249 | func (model *BCModel) AddNode(name, comment string) { 250 | layer := model.findLayer(name) 251 | layer.layer.Add(name, comment) 252 | } 253 | 254 | func (model *BCModel) AddRepoToLayer(layerName string, repo *Repository) { 255 | layer := model.Layers[layerName] 256 | layer.layer.(*RepoLayer).Repos[repo.name] = repo 257 | } 258 | 259 | func (model *BCModel) AddARToLayer(layerName string, ar *Entity) { 260 | layer := model.Layers[layerName] 261 | domainLayer := layer.layer.(*DomainLayer) 262 | domainLayer.ARs[ar.name] = ar 263 | //TODO: recursive entitys 264 | for _, entity := range ar.Entities { 265 | domainLayer.es[entity.name] = entity 266 | } 267 | for _, vo := range ar.VOs { 268 | domainLayer.vos[vo.name] = vo 269 | } 270 | } 271 | 272 | func (model *BCModel) AddServiceToLayer(layerName string, service *Service) { 273 | layer := model.Layers[layerName] 274 | layer.layer.(*ServiceLayer).Services[service.name] = service 275 | } 276 | 277 | func (model *BCModel) AddProviderToLayer(layerName string, provider *Provider) { 278 | layer := model.Layers[layerName] 279 | layer.layer.(*ServiceLayer).Providers[provider.name] = provider 280 | } 281 | 282 | func (layer *DomainLayer) addEntityRelations(src string, dsts []string) { 283 | entity, ok := layer.es[src] 284 | if !ok { 285 | return 286 | } 287 | for _, dst := range dsts { 288 | 289 | if et, ok := layer.es[dst]; ok { 290 | entity.Entities = append(entity.Entities, et) 291 | } 292 | if vo, ok := layer.vos[dst]; ok { 293 | entity.VOs = append(entity.VOs, vo) 294 | } 295 | } 296 | } 297 | func (layer *DomainLayer) addAggregateRootRelations(src string, dsts []string) { 298 | ar, ok := layer.ARs[src] 299 | if !ok { 300 | return 301 | } 302 | for _, dst := range dsts { 303 | 304 | if ref, ok := layer.ARs[dst]; ok { 305 | ar.Refs = append(ar.Refs, ref) 306 | } 307 | if et, ok := layer.es[dst]; ok { 308 | ar.Entities = append(ar.Entities, et) 309 | } 310 | if vo, ok := layer.vos[dst]; ok { 311 | ar.VOs = append(ar.VOs, vo) 312 | } 313 | } 314 | } 315 | 316 | func (layer *DomainLayer) addRelations(src string, dsts []string) { 317 | layer.addAggregateRootRelations(src, dsts) 318 | layer.addEntityRelations(src, dsts) 319 | } 320 | 321 | func (layer *RepoLayer) addRelations(src string, dsts []string) { 322 | repo, ok := layer.Repos[src] 323 | if ok { 324 | for _, dst := range dsts { 325 | repo.For = dst 326 | } 327 | } 328 | } 329 | 330 | func (layer *GatewayLayer) addRelations(src string, dsts []string) { 331 | gateway, ok := layer.GateWays[src] 332 | if ok { 333 | for _, dst := range dsts { 334 | gateway.Implement = dst 335 | } 336 | } 337 | } 338 | 339 | func (layer *ServiceLayer) addRelations(src string, dsts []string) { 340 | service, ok := layer.Services[src] 341 | if ok { 342 | for _, dst := range dsts { 343 | service.Refs = append(service.Refs, dst) 344 | } 345 | } 346 | } 347 | 348 | func (layer *ApiLayer) addRelations(src string, dsts []string) { 349 | api, ok := layer.Apis[src] 350 | if ok { 351 | for _, dst := range dsts { 352 | api.Refs = append(api.Refs, dst) 353 | } 354 | } 355 | } 356 | 357 | func (layer *DomainLayer) compare(other interface{}) bool { 358 | o := (other).(*DomainLayer) 359 | if len(o.ARs) != len(layer.ARs) { 360 | return false 361 | } 362 | for key := range layer.ARs { 363 | ar := layer.ARs[key] 364 | if !ar.Compare(o.ARs[key]) { 365 | return false 366 | } 367 | } 368 | return true 369 | } 370 | 371 | func (layer *RepoLayer) compare(other interface{}) bool { 372 | o := (other).(*RepoLayer) 373 | if len(o.Repos) != len(layer.Repos) { 374 | return false 375 | } 376 | for key := range layer.Repos { 377 | repo := layer.Repos[key] 378 | if !repo.Compare(o.Repos[key]) { 379 | return false 380 | } 381 | } 382 | return true 383 | } 384 | 385 | func (layer *ServiceLayer) compare(other interface{}) bool { 386 | o := (other).(*ServiceLayer) 387 | if len(o.Services) != len(layer.Services) { 388 | return false 389 | } 390 | for key := range layer.Services { 391 | service := layer.Services[key] 392 | if !service.compare(o.Services[key]) { 393 | return false 394 | } 395 | } 396 | return true 397 | } 398 | 399 | func (layer *GatewayLayer) compare(other interface{}) bool { 400 | return true 401 | } 402 | 403 | func (layer *ApiLayer) compare(other interface{}) bool { 404 | return true 405 | } 406 | 407 | func (model *BCModel) AddRelations(src string, dsts []string) { 408 | layer := model.findLayer(src) 409 | layer.layer.addRelations(src, dsts) 410 | } 411 | 412 | func (service *Service) compare(other *Service) bool { 413 | if len(service.Refs) != len(other.Refs) { 414 | return false 415 | } 416 | return true 417 | } 418 | func (layer *Layer) Compare(other *Layer) bool { 419 | return layer.layer.compare(other.layer) 420 | } 421 | func (model *BCModel) Compare(other *BCModel) error { 422 | if len(model.Layers) != len(other.Layers) { 423 | return errors.New("diff layer number") 424 | } 425 | for key := range model.Layers { 426 | layer := model.Layers[key] 427 | if !layer.Compare(other.Layers[key]) { 428 | return errors.New("layer: " + key + " is diff") 429 | } 430 | } 431 | return nil 432 | } 433 | --------------------------------------------------------------------------------