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 __gshared bool useBuildTaskDollarCurrent = false; 381 @protocolMethod("served/buildTasks") 382 Task[] provideBuildTasks() 383 { 384 Task[] ret; 385 foreach (instance; backend.instances) 386 { 387 if (!instance.has!DubComponent) 388 continue; 389 auto dub = instance.get!DubComponent; 390 auto workspace = .workspace(instance.cwd.uriFromFile, false); 391 info("Found dub package to build at ", dub.recipePath); 392 393 JSONValue dollarMagicValue; 394 if (useBuildTaskDollarCurrent) 395 dollarMagicValue = JSONValue("$current"); 396 397 JSONValue currentValue(string prop)() 398 { 399 if (useBuildTaskDollarCurrent) 400 return JSONValue("$current"); 401 else 402 return JSONValue(__traits(getMember, dub, prop)); 403 } 404 405 auto cwd = JSONValue(dub.recipePath.dirName.replace(workspace.folder.uri.uriToFile, "${workspaceFolder}")); 406 { 407 Task t; 408 t.source = "dub"; 409 t.definition = JSONValue([ 410 "type": JSONValue("dub"), 411 "run": JSONValue(true), 412 "compiler": currentValue!"compiler", 413 "archType": currentValue!"archType", 414 "buildType": currentValue!"buildType", 415 "configuration": currentValue!"configuration", 416 "cwd": cwd 417 ]); 418 t.group = Task.Group.build; 419 t.exec = [ 420 workspace.config.d.dubPath.userPath, "run", "--compiler=" ~ dub.compiler, 421 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 422 ].fixEmptyArgs; 423 t.scope_ = workspace.folder.uri; 424 t.name = "Run " ~ dub.name; 425 ret ~= t; 426 } 427 { 428 Task t; 429 t.source = "dub"; 430 t.definition = JSONValue([ 431 "type": JSONValue("dub"), 432 "test": JSONValue(true), 433 "compiler": currentValue!"compiler", 434 "archType": currentValue!"archType", 435 "buildType": currentValue!"buildType", 436 "configuration": currentValue!"configuration", 437 "cwd": cwd 438 ]); 439 t.group = Task.Group.test; 440 t.exec = [ 441 workspace.config.d.dubPath.userPath, "test", "--compiler=" ~ dub.compiler, 442 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 443 ].fixEmptyArgs; 444 t.scope_ = workspace.folder.uri; 445 t.name = "Test " ~ dub.name; 446 ret ~= t; 447 } 448 { 449 Task t; 450 t.source = "dub"; 451 t.definition = JSONValue([ 452 "type": JSONValue("dub"), 453 "run": JSONValue(false), 454 "compiler": currentValue!"compiler", 455 "archType": currentValue!"archType", 456 "buildType": currentValue!"buildType", 457 "configuration": currentValue!"configuration", 458 "cwd": cwd 459 ]); 460 t.group = Task.Group.build; 461 t.exec = [ 462 workspace.config.d.dubPath.userPath, "build", "--compiler=" ~ dub.compiler, 463 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 464 ].fixEmptyArgs; 465 t.scope_ = workspace.folder.uri; 466 t.name = "Build " ~ dub.name; 467 ret ~= t; 468 } 469 { 470 Task t; 471 t.source = "dub"; 472 t.definition = JSONValue([ 473 "type": JSONValue("dub"), 474 "run": JSONValue(false), 475 "force": JSONValue(true), 476 "compiler": currentValue!"compiler", 477 "archType": currentValue!"archType", 478 "buildType": currentValue!"buildType", 479 "configuration": currentValue!"configuration", 480 "cwd": cwd 481 ]); 482 t.group = Task.Group.rebuild; 483 t.exec = [ 484 workspace.config.d.dubPath.userPath, "build", "--force", 485 "--compiler=" ~ dub.compiler, "-a=" ~ dub.archType, 486 "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 487 ].fixEmptyArgs; 488 t.scope_ = workspace.folder.uri; 489 t.name = "Rebuild " ~ dub.name; 490 ret ~= t; 491 } 492 } 493 return ret; 494 } 495 496 // === Protocol Notifications starting here === 497 498 @protocolNotification("served/convertDubFormat") 499 void convertDubFormat(DubConvertRequest req) 500 { 501 import std.process : execute, Config; 502 503 auto file = req.textDocument.uri.uriToFile; 504 if (!fs.exists(file)) 505 { 506 error("Specified file does not exist"); 507 return; 508 } 509 510 if (!file.baseName.among!("dub.json", "dub.sdl", "package.json")) 511 { 512 rpc.window.showErrorMessage(translate!"d.dub.notRecipeFile"); 513 return; 514 } 515 516 auto document = documents[req.textDocument.uri]; 517 518 auto result = execute([ 519 workspace(req.textDocument.uri).config.d.dubPath.userPath, "convert", 520 "-f", req.newFormat, "-s" 521 ], null, Config.stderrPassThrough, 1024 * 1024 * 4, file.dirName); 522 523 if (result.status != 0) 524 { 525 rpc.window.showErrorMessage(translate!"d.dub.convertFailed"); 526 return; 527 } 528 529 auto newUri = req.textDocument.uri.setExtension("." ~ req.newFormat); 530 531 WorkspaceEdit edit; 532 auto edits = [ 533 TextEdit(TextRange(Position(0, 0), document.offsetToPosition(document.length)), result.output) 534 ]; 535 536 if (capabilities.workspace.workspaceEdit.resourceOperations.canFind(ResourceOperationKind.rename)) 537 { 538 edit.documentChanges = JSONValue([ 539 toJSON(RenameFile(req.textDocument.uri, newUri)), 540 toJSON(TextDocumentEdit(VersionedTextDocumentIdentifier(newUri, 541 document.version_), edits)) 542 ]); 543 } 544 else 545 edit.changes[req.textDocument.uri] = edits; 546 rpc.sendMethod("workspace/applyEdit", ApplyWorkspaceEditParams(edit)); 547 } 548 549 @protocolNotification("served/installDependency") 550 void installDependency(InstallRequest req) 551 { 552 auto instance = activeInstance; 553 auto uri = instance.cwd.uriFromFile; 554 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 555 injectDependency(instance, req); 556 if (instance.has!DubComponent) 557 instance.get!DubComponent.upgrade(); 558 reportProgress(ProgressType.dubReload, 7, 10, uri); 559 updateImports(UpdateImportsParams(false)); 560 reportProgress(ProgressType.dubReload, 10, 10, uri); 561 } 562 563 @protocolNotification("served/updateDependency") 564 void updateDependency(UpdateRequest req) 565 { 566 auto instance = activeInstance; 567 auto uri = instance.cwd.uriFromFile; 568 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 569 if (changeDependency(instance, req)) 570 { 571 if (instance.has!DubComponent) 572 instance.get!DubComponent.upgrade(); 573 reportProgress(ProgressType.dubReload, 7, 10, uri); 574 updateImports(UpdateImportsParams(false)); 575 } 576 reportProgress(ProgressType.dubReload, 10, 10, uri); 577 } 578 579 @protocolNotification("served/uninstallDependency") 580 void uninstallDependency(UninstallRequest req) 581 { 582 auto instance = activeInstance; 583 auto uri = instance.cwd.uriFromFile; 584 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 585 // TODO: add workspace argument 586 removeDependency(instance, req.name); 587 if (instance.has!DubComponent) 588 instance.get!DubComponent.upgrade(); 589 reportProgress(ProgressType.dubReload, 7, 10, uri); 590 updateImports(UpdateImportsParams(false)); 591 reportProgress(ProgressType.dubReload, 10, 10, uri); 592 } 593 594 void injectDependency(WorkspaceD.Instance instance, InstallRequest req) 595 { 596 auto sdl = buildPath(instance.cwd, "dub.sdl"); 597 if (fs.exists(sdl)) 598 { 599 int depth = 0; 600 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 601 auto insertAt = content.length; 602 bool gotLineEnding = false; 603 string lineEnding = "\n"; 604 foreach (i, line; content) 605 { 606 if (!gotLineEnding && line.length >= 2) 607 { 608 lineEnding = line[$ - 2 .. $]; 609 if (lineEnding[0] != '\r') 610 lineEnding = line[$ - 1 .. $]; 611 gotLineEnding = true; 612 } 613 if (depth == 0 && line.strip.startsWith("dependency ")) 614 insertAt = i + 1; 615 depth += line.count('{') - line.count('}'); 616 } 617 content = content[0 .. insertAt] ~ ((insertAt == content.length ? lineEnding 618 : "") ~ "dependency \"" ~ req.name ~ "\" version=\"~>" ~ req.version_ ~ "\"" ~ lineEnding) 619 ~ content[insertAt .. $]; 620 fs.write(sdl, content.join()); 621 } 622 else 623 { 624 auto json = buildPath(instance.cwd, "dub.json"); 625 if (!fs.exists(json)) 626 json = buildPath(instance.cwd, "package.json"); 627 if (!fs.exists(json)) 628 return; 629 auto content = fs.readText(json).splitLines(KeepTerminator.yes); 630 auto insertAt = content.length ? content.length - 1 : 0; 631 string lineEnding = "\n"; 632 bool gotLineEnding = false; 633 int depth = 0; 634 bool insertNext; 635 string indent; 636 bool foundBlock; 637 foreach (i, line; content) 638 { 639 if (!gotLineEnding && line.length >= 2) 640 { 641 lineEnding = line[$ - 2 .. $]; 642 if (lineEnding[0] != '\r') 643 lineEnding = line[$ - 1 .. $]; 644 gotLineEnding = true; 645 } 646 if (insertNext) 647 { 648 indent = line[0 .. $ - line.stripLeft.length]; 649 insertAt = i + 1; 650 break; 651 } 652 if (depth == 1 && line.strip.startsWith(`"dependencies":`)) 653 { 654 foundBlock = true; 655 if (line.strip.endsWith("{")) 656 { 657 indent = line[0 .. $ - line.stripLeft.length]; 658 insertAt = i + 1; 659 break; 660 } 661 else 662 { 663 insertNext = true; 664 } 665 } 666 depth += line.count('{') - line.count('}') + line.count('[') - line.count(']'); 667 } 668 if (foundBlock) 669 { 670 content = content[0 .. insertAt] ~ ( 671 indent ~ indent ~ `"` ~ req.name ~ `": "~>` ~ req.version_ ~ `",` ~ lineEnding) 672 ~ content[insertAt .. $]; 673 fs.write(json, content.join()); 674 } 675 else if (content.length) 676 { 677 if (content.length > 1) 678 content[$ - 2] = content[$ - 2].stripRight; 679 content = content[0 .. $ - 1] ~ ( 680 "," ~ lineEnding ~ ` "dependencies": { 681 "` ~ req.name ~ `": "~>` ~ req.version_ ~ `" 682 }` ~ lineEnding) 683 ~ content[$ - 1 .. $]; 684 fs.write(json, content.join()); 685 } 686 else 687 { 688 content ~= `{ 689 "dependencies": { 690 "` ~ req.name ~ `": "~>` ~ req.version_ ~ `" 691 } 692 }`; 693 fs.write(json, content.join()); 694 } 695 } 696 } 697 698 bool changeDependency(WorkspaceD.Instance instance, UpdateRequest req) 699 { 700 auto sdl = buildPath(instance.cwd, "dub.sdl"); 701 if (fs.exists(sdl)) 702 { 703 int depth = 0; 704 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 705 size_t target = size_t.max; 706 foreach (i, line; content) 707 { 708 if (depth == 0 && line.strip.startsWith("dependency ") 709 && line.strip["dependency".length .. $].strip.startsWith('"' ~ req.name ~ '"')) 710 { 711 target = i; 712 break; 713 } 714 depth += line.count('{') - line.count('}'); 715 } 716 if (target == size_t.max) 717 return false; 718 auto ver = content[target].indexOf("version"); 719 if (ver == -1) 720 return false; 721 auto quotStart = content[target].indexOf("\"", ver); 722 if (quotStart == -1) 723 return false; 724 auto quotEnd = content[target].indexOf("\"", quotStart + 1); 725 if (quotEnd == -1) 726 return false; 727 content[target] = content[target][0 .. quotStart] ~ '"' ~ req.version_ ~ '"' 728 ~ content[target][quotEnd .. $]; 729 fs.write(sdl, content.join()); 730 return true; 731 } 732 else 733 { 734 auto json = buildPath(instance.cwd, "dub.json"); 735 if (!fs.exists(json)) 736 json = buildPath(instance.cwd, "package.json"); 737 if (!fs.exists(json)) 738 return false; 739 auto content = fs.readText(json); 740 auto replaced = content.replaceFirst(regex(`("` ~ req.name ~ `"\s*:\s*)"[^"]*"`), 741 `$1"` ~ req.version_ ~ `"`); 742 if (content == replaced) 743 return false; 744 fs.write(json, replaced); 745 return true; 746 } 747 } 748 749 bool removeDependency(WorkspaceD.Instance instance, string name) 750 { 751 auto sdl = buildPath(instance.cwd, "dub.sdl"); 752 if (fs.exists(sdl)) 753 { 754 int depth = 0; 755 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 756 size_t target = size_t.max; 757 foreach (i, line; content) 758 { 759 if (depth == 0 && line.strip.startsWith("dependency ") 760 && line.strip["dependency".length .. $].strip.startsWith('"' ~ name ~ '"')) 761 { 762 target = i; 763 break; 764 } 765 depth += line.count('{') - line.count('}'); 766 } 767 if (target == size_t.max) 768 return false; 769 fs.write(sdl, (content[0 .. target] ~ content[target + 1 .. $]).join()); 770 return true; 771 } 772 else 773 { 774 auto json = buildPath(instance.cwd, "dub.json"); 775 if (!fs.exists(json)) 776 json = buildPath(instance.cwd, "package.json"); 777 if (!fs.exists(json)) 778 return false; 779 auto content = fs.readText(json); 780 auto replaced = content.replaceFirst(regex(`"` ~ name ~ `"\s*:\s*"[^"]*"\s*,\s*`), ""); 781 if (content == replaced) 782 replaced = content.replaceFirst(regex(`\s*,\s*"` ~ name ~ `"\s*:\s*"[^"]*"`), ""); 783 if (content == replaced) 784 replaced = content.replaceFirst(regex( 785 `"dependencies"\s*:\s*\{\s*"` ~ name ~ `"\s*:\s*"[^"]*"\s*\}\s*,\s*`), ""); 786 if (content == replaced) 787 replaced = content.replaceFirst(regex( 788 `\s*,\s*"dependencies"\s*:\s*\{\s*"` ~ name ~ `"\s*:\s*"[^"]*"\s*\}`), ""); 789 if (content == replaced) 790 return false; 791 fs.write(json, replaced); 792 return true; 793 } 794 }