1 module served.commands.dub; 2 3 import served.extension; 4 import served.types; 5 import served.utils.progress; 6 import served.utils.translate; 7 8 import workspaced.api; 9 import workspaced.coms; 10 11 import painlessjson : toJSON; 12 13 import core.time; 14 15 import std.algorithm : among, canFind, count, endsWith, map, remove, startsWith; 16 import std.array : array, replace; 17 import std.experimental.logger; 18 import std.json : JSONValue; 19 import std.path : baseName, buildPath, dirName, setExtension; 20 import std.regex : regex, replaceFirst; 21 import std..string : indexOf, join, KeepTerminator, splitLines, strip, stripLeft, stripRight; 22 23 import fs = std.file; 24 import io = std.stdio; 25 26 @protocolMethod("served/listConfigurations") 27 string[] listConfigurations() 28 { 29 if (!activeInstance || !activeInstance.has!DubComponent) 30 return null; 31 return activeInstance.get!DubComponent.configurations; 32 } 33 34 @protocolMethod("served/switchConfig") 35 bool switchConfig(string value) 36 { 37 if (!activeInstance || !activeInstance.has!DubComponent) 38 return false; 39 return activeInstance.get!DubComponent.setConfiguration(value); 40 } 41 42 @protocolMethod("served/getConfig") 43 string getConfig() 44 { 45 if (!activeInstance || !activeInstance.has!DubComponent) 46 return null; 47 return activeInstance.get!DubComponent.configuration; 48 } 49 50 @protocolMethod("served/listArchTypes") 51 string[] listArchTypes() 52 { 53 if (!activeInstance || !activeInstance.has!DubComponent) 54 return null; 55 return activeInstance.get!DubComponent.archTypes; 56 } 57 58 @protocolMethod("served/switchArchType") 59 bool switchArchType(string value) 60 { 61 if (!activeInstance || !activeInstance.has!DubComponent) 62 return false; 63 return activeInstance.get!DubComponent.setArchType(JSONValue([ 64 "arch-type": JSONValue(value) 65 ])); 66 } 67 68 @protocolMethod("served/getArchType") 69 string getArchType() 70 { 71 if (!activeInstance || !activeInstance.has!DubComponent) 72 return null; 73 return activeInstance.get!DubComponent.archType; 74 } 75 76 @protocolMethod("served/listBuildTypes") 77 string[] listBuildTypes() 78 { 79 if (!activeInstance || !activeInstance.has!DubComponent) 80 return null; 81 return activeInstance.get!DubComponent.buildTypes; 82 } 83 84 @protocolMethod("served/switchBuildType") 85 bool switchBuildType(string value) 86 { 87 if (!activeInstance || !activeInstance.has!DubComponent) 88 return false; 89 return activeInstance.get!DubComponent.setBuildType(JSONValue([ 90 "build-type": JSONValue(value) 91 ])); 92 } 93 94 @protocolMethod("served/getBuildType") 95 string getBuildType() 96 { 97 if (!activeInstance || !activeInstance.has!DubComponent) 98 return null; 99 return activeInstance.get!DubComponent.buildType; 100 } 101 102 @protocolMethod("served/getCompiler") 103 string getCompiler() 104 { 105 if (!activeInstance || !activeInstance.has!DubComponent) 106 return null; 107 return activeInstance.get!DubComponent.compiler; 108 } 109 110 @protocolMethod("served/switchCompiler") 111 bool switchCompiler(string value) 112 { 113 if (!activeInstance || !activeInstance.has!DubComponent) 114 return false; 115 return activeInstance.get!DubComponent.setCompiler(value); 116 } 117 118 /// Returns: at least 119 /// ``` 120 /// { 121 /// "packagePath": string, 122 /// "packageName": string, 123 /// "recipePath": string, 124 /// "targetPath": string, 125 /// "targetName": string, 126 /// "targetType": string, 127 /// "workingDirectory": string, 128 /// "mainSourceFile": string, 129 /// 130 /// "dflags": string[], 131 /// "lflags": string[], 132 /// "libs": string[], 133 /// "linkerFiles": string[], 134 /// "sourceFiles": string[], 135 /// "copyFiles": string[], 136 /// "versions": string[], 137 /// "debugVersions": string[], 138 /// "importPaths": string[], 139 /// "stringImportPaths": string[], 140 /// "importFiles": string[], 141 /// "stringImportFiles": string[], 142 /// "preGenerateCommands": string[], 143 /// "postGenerateCommands": string[], 144 /// "preBuildCommands": string[], 145 /// "postBuildCommands": string[], 146 /// "preRunCommands": string[], 147 /// "postRunCommands": string[], 148 /// "buildOptions": string[], 149 /// "buildRequirements": string[], 150 /// } 151 /// ``` 152 @protocolMethod("served/getActiveDubConfig") 153 JSONValue getActiveDubConfig() 154 { 155 if (!activeInstance || !activeInstance.has!DubComponent) 156 return JSONValue.init; 157 auto ret = activeInstance.get!DubComponent.rootPackageBuildSettings(); 158 static assert(is(typeof(ret.packagePath) : string), "API guarantee broken"); 159 static assert(is(typeof(ret.packageName) : string), "API guarantee broken"); 160 static assert(is(typeof(ret.recipePath) : string), "API guarantee broken"); 161 static assert(is(typeof(ret.targetPath) : string), "API guarantee broken"); 162 static assert(is(typeof(ret.targetName) : string), "API guarantee broken"); 163 static assert(is(typeof(ret.targetType) : string), "API guarantee broken"); 164 static assert(is(typeof(ret.workingDirectory) : string), "API guarantee broken"); 165 static assert(is(typeof(ret.mainSourceFile) : string), "API guarantee broken"); 166 static assert(is(typeof(ret.dflags) : string[]), "API guarantee broken"); 167 static assert(is(typeof(ret.lflags) : string[]), "API guarantee broken"); 168 static assert(is(typeof(ret.libs) : string[]), "API guarantee broken"); 169 static assert(is(typeof(ret.linkerFiles) : string[]), "API guarantee broken"); 170 static assert(is(typeof(ret.sourceFiles) : string[]), "API guarantee broken"); 171 static assert(is(typeof(ret.copyFiles) : string[]), "API guarantee broken"); 172 static assert(is(typeof(ret.versions) : string[]), "API guarantee broken"); 173 static assert(is(typeof(ret.debugVersions) : string[]), "API guarantee broken"); 174 static assert(is(typeof(ret.importPaths) : string[]), "API guarantee broken"); 175 static assert(is(typeof(ret.stringImportPaths) : string[]), "API guarantee broken"); 176 static assert(is(typeof(ret.importFiles) : string[]), "API guarantee broken"); 177 static assert(is(typeof(ret.stringImportFiles) : string[]), "API guarantee broken"); 178 static assert(is(typeof(ret.preGenerateCommands) : string[]), "API guarantee broken"); 179 static assert(is(typeof(ret.postGenerateCommands) : string[]), "API guarantee broken"); 180 static assert(is(typeof(ret.preBuildCommands) : string[]), "API guarantee broken"); 181 static assert(is(typeof(ret.postBuildCommands) : string[]), "API guarantee broken"); 182 static assert(is(typeof(ret.preRunCommands) : string[]), "API guarantee broken"); 183 static assert(is(typeof(ret.postRunCommands) : string[]), "API guarantee broken"); 184 static assert(is(typeof(ret.buildOptions) : string[]), "API guarantee broken"); 185 static assert(is(typeof(ret.buildRequirements) : string[]), "API guarantee broken"); 186 return ret.toJSON; 187 } 188 189 @protocolMethod("served/addImport") 190 auto addImport(AddImportParams params) 191 { 192 auto document = documents[params.textDocument.uri]; 193 return backend.get!ImporterComponent.add(params.name.idup, document.rawText, 194 params.location, params.insertOutermost); 195 } 196 197 @protocolMethod("served/updateImports") 198 bool updateImports(UpdateImportsParams params) 199 { 200 auto instance = activeInstance; 201 bool success; 202 203 reportProgress(params.reportProgress, ProgressType.dubReload, 0, 5, instance.cwd.uriFromFile); 204 205 if (instance.has!DubComponent) 206 { 207 success = instance.get!DubComponent.update.getYield; 208 if (success) 209 rpc.notifyMethod("coded/updateDubTree"); 210 } 211 reportProgress(params.reportProgress, ProgressType.importReload, 4, 5, instance.cwd.uriFromFile); 212 if (instance.has!DCDComponent) 213 instance.get!DCDComponent.refreshImports(); 214 reportProgress(params.reportProgress, ProgressType.importReload, 5, 5, instance.cwd.uriFromFile); 215 return success; 216 } 217 218 @protocolNotification("textDocument/didSave") 219 void onDidSaveDubRecipe(DidSaveTextDocumentParams params) 220 { 221 auto fileName = params.textDocument.uri.uriToFile.baseName; 222 if (!fileName.among!("dub.json", "dub.sdl")) 223 return; 224 225 auto workspaceUri = workspace(params.textDocument.uri).folder.uri; 226 auto workspaceRoot = workspaceUri.uriToFile; 227 228 info("Updating dependencies"); 229 reportProgress(ProgressType.importUpgrades, 0, 10, workspaceUri); 230 if (!backend.has!DubComponent(workspaceRoot)) 231 { 232 Exception err; 233 const success = backend.attach(backend.getInstance(workspaceRoot), "dub", err); 234 if (!success) 235 { 236 rpc.window.showMessage(MessageType.error, translate!"d.ext.dubUpgradeFail"); 237 error(err); 238 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 239 return; 240 } 241 } 242 else 243 { 244 if (backend.get!DubComponent(workspaceRoot).isRunning) 245 { 246 string syntaxCheck = backend.get!DubComponent(workspaceRoot) 247 .validateRecipeSyntaxOnFileSystem(); 248 249 if (syntaxCheck.length) 250 { 251 rpc.window.showMessage(MessageType.error, 252 translate!"d.ext.dubInvalidRecipeSyntax"(syntaxCheck)); 253 error(syntaxCheck); 254 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 255 return; 256 } 257 258 rpc.window.runOrMessage(backend.get!DubComponent(workspaceRoot) 259 .upgrade(), MessageType.warning, translate!"d.ext.dubUpgradeFail"); 260 } 261 else 262 { 263 rpc.window.showMessage(MessageType.error, translate!"d.ext.dubUpgradeFail"); 264 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 265 return; 266 } 267 } 268 reportProgress(ProgressType.importUpgrades, 6, 10, workspaceUri); 269 270 setTimeout({ 271 const successfulUpdate = rpc.window.runOrMessage(backend.get!DubComponent(workspaceRoot) 272 .updateImportPaths(true), MessageType.warning, translate!"d.ext.dubImportFail"); 273 if (successfulUpdate) 274 { 275 rpc.window.runOrMessage(updateImports(UpdateImportsParams(false)), 276 MessageType.warning, translate!"d.ext.dubImportFail"); 277 } 278 else 279 { 280 try 281 { 282 updateImports(UpdateImportsParams(false)); 283 } 284 catch (Exception e) 285 { 286 errorf("Failed updating imports: %s", e); 287 } 288 } 289 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 290 }, 200.msecs); 291 292 setTimeout({ 293 if (!backend.get!DubComponent(workspaceRoot).isRunning) 294 { 295 Exception err; 296 if (backend.attach(backend.getInstance(workspaceRoot), "dub", err)) 297 { 298 rpc.window.runOrMessage(backend.get!DubComponent(workspaceRoot) 299 .updateImportPaths(true), MessageType.warning, 300 translate!"d.ext.dubRecipeMaybeBroken"); 301 error(err); 302 } 303 } 304 }, 500.msecs); 305 rpc.notifyMethod("coded/updateDubTree"); 306 } 307 308 @protocolMethod("served/listDependencies") 309 DubDependency[] listDependencies(string packageName) 310 { 311 auto instance = activeInstance; 312 DubDependency[] ret; 313 if (!instance.has!DubComponent) 314 return ret; 315 316 auto allDeps = instance.get!DubComponent.dependencies; 317 if (!packageName.length) 318 { 319 auto deps = instance.get!DubComponent.rootDependencies; 320 foreach (dep; deps) 321 { 322 DubDependency r; 323 r.name = dep; 324 r.root = true; 325 foreach (other; allDeps) 326 if (other.name == dep) 327 { 328 r.version_ = other.ver; 329 r.path = other.path; 330 r.description = other.description; 331 r.homepage = other.homepage; 332 r.authors = other.authors; 333 r.copyright = other.copyright; 334 r.license = other.license; 335 r.subPackages = other.subPackages.map!"a.name".array; 336 r.hasDependencies = other.dependencies.length > 0; 337 break; 338 } 339 ret ~= r; 340 } 341 } 342 else 343 { 344 string[string] aa; 345 foreach (other; allDeps) 346 if (other.name == packageName) 347 { 348 aa = other.dependencies; 349 break; 350 } 351 foreach (name, ver; aa) 352 { 353 DubDependency r; 354 r.name = name; 355 r.version_ = ver; 356 foreach (other; allDeps) 357 if (other.name == name) 358 { 359 r.path = other.path; 360 r.description = other.description; 361 r.homepage = other.homepage; 362 r.authors = other.authors; 363 r.copyright = other.copyright; 364 r.license = other.license; 365 r.subPackages = other.subPackages.map!"a.name".array; 366 r.hasDependencies = other.dependencies.length > 0; 367 break; 368 } 369 ret ~= r; 370 } 371 } 372 return ret; 373 } 374 375 private string[] fixEmptyArgs(string[] args) 376 { 377 return args.remove!(a => a.endsWith('=')); 378 } 379 380 @protocolMethod("served/buildTasks") 381 Task[] provideBuildTasks() 382 { 383 Task[] ret; 384 foreach (instance; backend.instances) 385 { 386 if (!instance.has!DubComponent) 387 continue; 388 auto dub = instance.get!DubComponent; 389 auto workspace = .workspace(instance.cwd.uriFromFile, false); 390 info("Found dub package to build at ", dub.recipePath); 391 JSONValue currentMagicValue = JSONValue("$current"); 392 auto cwd = JSONValue(dub.recipePath.dirName.replace(workspace.folder.uri.uriToFile, "${workspaceFolder}")); 393 { 394 Task t; 395 t.source = "dub"; 396 t.definition = JSONValue([ 397 "type": JSONValue("dub"), 398 "run": JSONValue(true), 399 "compiler": currentMagicValue, 400 "archType": currentMagicValue, 401 "buildType": currentMagicValue, 402 "configuration": currentMagicValue, 403 "cwd": cwd 404 ]); 405 t.group = Task.Group.build; 406 t.exec = [ 407 workspace.config.d.dubPath.userPath, "run", "--compiler=" ~ dub.compiler, 408 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 409 ].fixEmptyArgs; 410 t.scope_ = workspace.folder.uri; 411 t.name = "Run " ~ dub.name; 412 ret ~= t; 413 } 414 { 415 Task t; 416 t.source = "dub"; 417 t.definition = JSONValue([ 418 "type": JSONValue("dub"), 419 "test": JSONValue(true), 420 "compiler": currentMagicValue, 421 "archType": currentMagicValue, 422 "buildType": currentMagicValue, 423 "configuration": currentMagicValue, 424 "cwd": cwd 425 ]); 426 t.group = Task.Group.test; 427 t.exec = [ 428 workspace.config.d.dubPath.userPath, "test", "--compiler=" ~ dub.compiler, 429 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 430 ].fixEmptyArgs; 431 t.scope_ = workspace.folder.uri; 432 t.name = "Test " ~ dub.name; 433 ret ~= t; 434 } 435 { 436 Task t; 437 t.source = "dub"; 438 t.definition = JSONValue([ 439 "type": JSONValue("dub"), 440 "run": JSONValue(false), 441 "compiler": currentMagicValue, 442 "archType": currentMagicValue, 443 "buildType": currentMagicValue, 444 "configuration": currentMagicValue, 445 "cwd": cwd 446 ]); 447 t.group = Task.Group.build; 448 t.exec = [ 449 workspace.config.d.dubPath.userPath, "build", "--compiler=" ~ dub.compiler, 450 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 451 ].fixEmptyArgs; 452 t.scope_ = workspace.folder.uri; 453 t.name = "Build " ~ dub.name; 454 ret ~= t; 455 } 456 { 457 Task t; 458 t.source = "dub"; 459 t.definition = JSONValue([ 460 "type": JSONValue("dub"), 461 "run": JSONValue(false), 462 "force": JSONValue(true), 463 "compiler": currentMagicValue, 464 "archType": currentMagicValue, 465 "buildType": currentMagicValue, 466 "configuration": currentMagicValue, 467 "cwd": cwd 468 ]); 469 t.group = Task.Group.rebuild; 470 t.exec = [ 471 workspace.config.d.dubPath.userPath, "build", "--force", 472 "--compiler=" ~ dub.compiler, "-a=" ~ dub.archType, 473 "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 474 ].fixEmptyArgs; 475 t.scope_ = workspace.folder.uri; 476 t.name = "Rebuild " ~ dub.name; 477 ret ~= t; 478 } 479 } 480 return ret; 481 } 482 483 // === Protocol Notifications starting here === 484 485 @protocolNotification("served/convertDubFormat") 486 void convertDubFormat(DubConvertRequest req) 487 { 488 import std.process : execute, Config; 489 490 auto file = req.textDocument.uri.uriToFile; 491 if (!fs.exists(file)) 492 { 493 error("Specified file does not exist"); 494 return; 495 } 496 497 if (!file.baseName.among!("dub.json", "dub.sdl", "package.json")) 498 { 499 rpc.window.showErrorMessage(translate!"d.dub.notRecipeFile"); 500 return; 501 } 502 503 auto document = documents[req.textDocument.uri]; 504 505 auto result = execute([ 506 workspace(req.textDocument.uri).config.d.dubPath.userPath, "convert", 507 "-f", req.newFormat, "-s" 508 ], null, Config.stderrPassThrough, 1024 * 1024 * 4, file.dirName); 509 510 if (result.status != 0) 511 { 512 rpc.window.showErrorMessage(translate!"d.dub.convertFailed"); 513 return; 514 } 515 516 auto newUri = req.textDocument.uri.setExtension("." ~ req.newFormat); 517 518 WorkspaceEdit edit; 519 auto edits = [ 520 TextEdit(TextRange(Position(0, 0), document.offsetToPosition(document.length)), result.output) 521 ]; 522 523 if (capabilities.workspace.workspaceEdit.resourceOperations.canFind(ResourceOperationKind.rename)) 524 { 525 edit.documentChanges = JSONValue([ 526 toJSON(RenameFile(req.textDocument.uri, newUri)), 527 toJSON(TextDocumentEdit(VersionedTextDocumentIdentifier(newUri, 528 document.version_), edits)) 529 ]); 530 } 531 else 532 edit.changes[req.textDocument.uri] = edits; 533 rpc.sendMethod("workspace/applyEdit", ApplyWorkspaceEditParams(edit)); 534 } 535 536 @protocolNotification("served/installDependency") 537 void installDependency(InstallRequest req) 538 { 539 auto instance = activeInstance; 540 auto uri = instance.cwd.uriFromFile; 541 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 542 injectDependency(instance, req); 543 if (instance.has!DubComponent) 544 instance.get!DubComponent.upgrade(); 545 reportProgress(ProgressType.dubReload, 7, 10, uri); 546 updateImports(UpdateImportsParams(false)); 547 reportProgress(ProgressType.dubReload, 10, 10, uri); 548 } 549 550 @protocolNotification("served/updateDependency") 551 void updateDependency(UpdateRequest req) 552 { 553 auto instance = activeInstance; 554 auto uri = instance.cwd.uriFromFile; 555 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 556 if (changeDependency(instance, req)) 557 { 558 if (instance.has!DubComponent) 559 instance.get!DubComponent.upgrade(); 560 reportProgress(ProgressType.dubReload, 7, 10, uri); 561 updateImports(UpdateImportsParams(false)); 562 } 563 reportProgress(ProgressType.dubReload, 10, 10, uri); 564 } 565 566 @protocolNotification("served/uninstallDependency") 567 void uninstallDependency(UninstallRequest req) 568 { 569 auto instance = activeInstance; 570 auto uri = instance.cwd.uriFromFile; 571 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 572 // TODO: add workspace argument 573 removeDependency(instance, req.name); 574 if (instance.has!DubComponent) 575 instance.get!DubComponent.upgrade(); 576 reportProgress(ProgressType.dubReload, 7, 10, uri); 577 updateImports(UpdateImportsParams(false)); 578 reportProgress(ProgressType.dubReload, 10, 10, uri); 579 } 580 581 void injectDependency(WorkspaceD.Instance instance, InstallRequest req) 582 { 583 auto sdl = buildPath(instance.cwd, "dub.sdl"); 584 if (fs.exists(sdl)) 585 { 586 int depth = 0; 587 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 588 auto insertAt = content.length; 589 bool gotLineEnding = false; 590 string lineEnding = "\n"; 591 foreach (i, line; content) 592 { 593 if (!gotLineEnding && line.length >= 2) 594 { 595 lineEnding = line[$ - 2 .. $]; 596 if (lineEnding[0] != '\r') 597 lineEnding = line[$ - 1 .. $]; 598 gotLineEnding = true; 599 } 600 if (depth == 0 && line.strip.startsWith("dependency ")) 601 insertAt = i + 1; 602 depth += line.count('{') - line.count('}'); 603 } 604 content = content[0 .. insertAt] ~ ((insertAt == content.length ? lineEnding 605 : "") ~ "dependency \"" ~ req.name ~ "\" version=\"~>" ~ req.version_ ~ "\"" ~ lineEnding) 606 ~ content[insertAt .. $]; 607 fs.write(sdl, content.join()); 608 } 609 else 610 { 611 auto json = buildPath(instance.cwd, "dub.json"); 612 if (!fs.exists(json)) 613 json = buildPath(instance.cwd, "package.json"); 614 if (!fs.exists(json)) 615 return; 616 auto content = fs.readText(json).splitLines(KeepTerminator.yes); 617 auto insertAt = content.length ? content.length - 1 : 0; 618 string lineEnding = "\n"; 619 bool gotLineEnding = false; 620 int depth = 0; 621 bool insertNext; 622 string indent; 623 bool foundBlock; 624 foreach (i, line; content) 625 { 626 if (!gotLineEnding && line.length >= 2) 627 { 628 lineEnding = line[$ - 2 .. $]; 629 if (lineEnding[0] != '\r') 630 lineEnding = line[$ - 1 .. $]; 631 gotLineEnding = true; 632 } 633 if (insertNext) 634 { 635 indent = line[0 .. $ - line.stripLeft.length]; 636 insertAt = i + 1; 637 break; 638 } 639 if (depth == 1 && line.strip.startsWith(`"dependencies":`)) 640 { 641 foundBlock = true; 642 if (line.strip.endsWith("{")) 643 { 644 indent = line[0 .. $ - line.stripLeft.length]; 645 insertAt = i + 1; 646 break; 647 } 648 else 649 { 650 insertNext = true; 651 } 652 } 653 depth += line.count('{') - line.count('}') + line.count('[') - line.count(']'); 654 } 655 if (foundBlock) 656 { 657 content = content[0 .. insertAt] ~ ( 658 indent ~ indent ~ `"` ~ req.name ~ `": "~>` ~ req.version_ ~ `",` ~ lineEnding) 659 ~ content[insertAt .. $]; 660 fs.write(json, content.join()); 661 } 662 else if (content.length) 663 { 664 if (content.length > 1) 665 content[$ - 2] = content[$ - 2].stripRight; 666 content = content[0 .. $ - 1] ~ ( 667 "," ~ lineEnding ~ ` "dependencies": { 668 "` ~ req.name ~ `": "~>` ~ req.version_ ~ `" 669 }` ~ lineEnding) 670 ~ content[$ - 1 .. $]; 671 fs.write(json, content.join()); 672 } 673 else 674 { 675 content ~= `{ 676 "dependencies": { 677 "` ~ req.name ~ `": "~>` ~ req.version_ ~ `" 678 } 679 }`; 680 fs.write(json, content.join()); 681 } 682 } 683 } 684 685 bool changeDependency(WorkspaceD.Instance instance, UpdateRequest req) 686 { 687 auto sdl = buildPath(instance.cwd, "dub.sdl"); 688 if (fs.exists(sdl)) 689 { 690 int depth = 0; 691 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 692 size_t target = size_t.max; 693 foreach (i, line; content) 694 { 695 if (depth == 0 && line.strip.startsWith("dependency ") 696 && line.strip["dependency".length .. $].strip.startsWith('"' ~ req.name ~ '"')) 697 { 698 target = i; 699 break; 700 } 701 depth += line.count('{') - line.count('}'); 702 } 703 if (target == size_t.max) 704 return false; 705 auto ver = content[target].indexOf("version"); 706 if (ver == -1) 707 return false; 708 auto quotStart = content[target].indexOf("\"", ver); 709 if (quotStart == -1) 710 return false; 711 auto quotEnd = content[target].indexOf("\"", quotStart + 1); 712 if (quotEnd == -1) 713 return false; 714 content[target] = content[target][0 .. quotStart] ~ '"' ~ req.version_ ~ '"' 715 ~ content[target][quotEnd .. $]; 716 fs.write(sdl, content.join()); 717 return true; 718 } 719 else 720 { 721 auto json = buildPath(instance.cwd, "dub.json"); 722 if (!fs.exists(json)) 723 json = buildPath(instance.cwd, "package.json"); 724 if (!fs.exists(json)) 725 return false; 726 auto content = fs.readText(json); 727 auto replaced = content.replaceFirst(regex(`("` ~ req.name ~ `"\s*:\s*)"[^"]*"`), 728 `$1"` ~ req.version_ ~ `"`); 729 if (content == replaced) 730 return false; 731 fs.write(json, replaced); 732 return true; 733 } 734 } 735 736 bool removeDependency(WorkspaceD.Instance instance, string name) 737 { 738 auto sdl = buildPath(instance.cwd, "dub.sdl"); 739 if (fs.exists(sdl)) 740 { 741 int depth = 0; 742 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 743 size_t target = size_t.max; 744 foreach (i, line; content) 745 { 746 if (depth == 0 && line.strip.startsWith("dependency ") 747 && line.strip["dependency".length .. $].strip.startsWith('"' ~ name ~ '"')) 748 { 749 target = i; 750 break; 751 } 752 depth += line.count('{') - line.count('}'); 753 } 754 if (target == size_t.max) 755 return false; 756 fs.write(sdl, (content[0 .. target] ~ content[target + 1 .. $]).join()); 757 return true; 758 } 759 else 760 { 761 auto json = buildPath(instance.cwd, "dub.json"); 762 if (!fs.exists(json)) 763 json = buildPath(instance.cwd, "package.json"); 764 if (!fs.exists(json)) 765 return false; 766 auto content = fs.readText(json); 767 auto replaced = content.replaceFirst(regex(`"` ~ name ~ `"\s*:\s*"[^"]*"\s*,\s*`), ""); 768 if (content == replaced) 769 replaced = content.replaceFirst(regex(`\s*,\s*"` ~ name ~ `"\s*:\s*"[^"]*"`), ""); 770 if (content == replaced) 771 replaced = content.replaceFirst(regex( 772 `"dependencies"\s*:\s*\{\s*"` ~ name ~ `"\s*:\s*"[^"]*"\s*\}\s*,\s*`), ""); 773 if (content == replaced) 774 replaced = content.replaceFirst(regex( 775 `\s*,\s*"dependencies"\s*:\s*\{\s*"` ~ name ~ `"\s*:\s*"[^"]*"\s*\}`), ""); 776 if (content == replaced) 777 return false; 778 fs.write(json, replaced); 779 return true; 780 } 781 }