├── .gitattributes ├── Documentation ├── GameframeFace.gif └── GameframeFace.gif.meta ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── Template.meta ├── Template ├── Licenses │ ├── MIT LICENSE.meta │ └── MIT LICENSE ├── README_TEMPLATE.md.meta ├── Licenses.meta └── README_TEMPLATE.md ├── Tests.meta ├── Documentation.meta ├── Editor ├── Utility.meta ├── PackageNpmPublisher.meta ├── com.gameframe.packages.editor.asmdef.meta ├── PackageMenu.cs.meta ├── PackageUtility.cs.meta ├── ShellUtility.cs.meta ├── SimpleJson.cs.meta ├── PackageGuiUtility.cs.meta ├── PackageManifest.cs.meta ├── PackageSettings.cs.meta ├── SourcePackageInfo.cs.meta ├── PackageMaintainerWindow.cs.meta ├── Utility │ ├── AsyncOperationAwaiter.cs.meta │ ├── RequestExtensions.cs.meta │ ├── RequestExtensions.cs │ └── AsyncOperationAwaiter.cs ├── PackageNpmPublisher │ ├── PackageNpmPublisher.cs.meta │ ├── PackageNpmPublisher.uss.meta │ ├── PackageNpmPublisher.uss │ ├── PackageNpmPublisher.uxml.meta │ ├── PackageNpmPublisher.uxml │ └── PackageNpmPublisher.cs ├── com.gameframe.packages.editor.asmdef ├── PackageSettings.cs ├── PackageManifest.cs ├── PackageGuiUtility.cs ├── SourcePackageInfo.cs ├── PackageMenu.cs ├── PackageUtility.cs ├── ShellUtility.cs ├── PackageMaintainerWindow.cs └── SimpleJson.cs ├── Tests ├── com.gameframe.packages.tests.asmdef.meta ├── ShellUtilityTests.cs.meta ├── com.gameframe.packages.tests.asmdef └── ShellUtilityTests.cs ├── package.json ├── LICENSE.md ├── .gitignore └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Documentation/GameframeFace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coryleach/UnityPackages/HEAD/Documentation/GameframeFace.gif -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5c0692abccabdd14ca1bb7fe801b4593 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 971d2fdde302f7341a6c865eed9d198b 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f93fa2b2db0bfa64189288df110373c6 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75b3a24d1fb821346963c52b201659d1 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Template.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 154b28759d40efb44bca8538c157d9a8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Template/Licenses/MIT LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1470dfe4373a4994db2f39a13610b4d0 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Template/README_TEMPLATE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5d9523bf82d25a84c953bfb49219f0d3 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 860ad12e0060de94d8e59774913ae548 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Documentation.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dda4c03853fec46bebff0e3d416ad74b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Utility.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd8b9a4fdf7cee946864833b9e91bdc8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Template/Licenses.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a4dec6f1706f123488eaba8ed9e00415 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/PackageNpmPublisher.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ed3c59643e58ba48af6dd9a0b7079b5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/com.gameframe.packages.editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8774ef0c0d363ae48b23b3dd950d3560 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests/com.gameframe.packages.tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8c4ce575f3f7d4c59b79cfcce5d62cd9 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/PackageMenu.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4b393b3ddb3a6f3409a8cb3e5a1f98d9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PackageUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 46a0693646c231c4dafc66fb56a82fa7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ShellUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 595c963e009227f479fc09c643385cdc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SimpleJson.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7daa7c68c94dea84abfd91a2151dd115 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PackageGuiUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3edb703b81145154489cdbda90c4fee0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PackageManifest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 98e17d44e578e0243926fef6ba272cd1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PackageSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7fedcd0de29839e4ba6cb907d2f7ca0e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SourcePackageInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 80b3d24ddf142d243bd02d26a49d9284 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/ShellUtilityTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b1ce4de404aa4bd4b9fff05b54f35cfa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PackageMaintainerWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e15605124527e942b4a6286dbc4e1de 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Utility/AsyncOperationAwaiter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: abe3b007b34d81545b88b46c62b57305 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Utility/RequestExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f48bb13575db7b540a49a8c2bac83aa1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PackageNpmPublisher/PackageNpmPublisher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 166f81a40c9f16a49a9713d5d720f2c4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Utility/RequestExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Gameframe.Packages.Utility 2 | { 3 | public static class RequestExtensions 4 | { 5 | public static AsyncOperationAwaiter GetAwaiter(this UnityEditor.PackageManager.Requests.Request request) 6 | { 7 | return new AsyncOperationAwaiter(()=>request.IsCompleted); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Editor/PackageNpmPublisher/PackageNpmPublisher.uss.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d91d9be09a558f4aa7ee17e557dbd5f 3 | ScriptedImporter: 4 | fileIDToRecycleName: 5 | 11400000: stylesheet 6 | externalObjects: {} 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} 11 | -------------------------------------------------------------------------------- /Editor/PackageNpmPublisher/PackageNpmPublisher.uss: -------------------------------------------------------------------------------- 1 | .BorderedContainer 2 | { 3 | margin:5px; 4 | border-width:1px; 5 | border-color: rgba(0,0,0,0.5); 6 | background-color: rgba(0,0,0,0.2); 7 | border-radius: 5px; 8 | padding:5px; 9 | } 10 | 11 | .rowEven 12 | { 13 | background-color: rgba(0,0,0,0.20) 14 | } 15 | 16 | .rowOdd 17 | { 18 | background-color: rgba(0,0,0,0.30) 19 | } 20 | -------------------------------------------------------------------------------- /Editor/PackageNpmPublisher/PackageNpmPublisher.uxml.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c79b1e62247e9b04d91e74f8cc1b97cc 3 | ScriptedImporter: 4 | fileIDToRecycleName: 5 | 11400000: tree 6 | 11400002: inlineStyle 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} 12 | -------------------------------------------------------------------------------- /Editor/com.gameframe.packages.editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.gameframe.packages.editor", 3 | "references": [ 4 | "com.gameframe.packages.runtime" 5 | ], 6 | "optionalUnityReferences": [], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [] 16 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.gameframe.packages", 3 | "version": "1.0.2", 4 | "displayName": "Gameframe.Packages", 5 | "description": "Package for creating Unity packages just like this one!", 6 | "repositoryName": "UnityPackages", 7 | "author": { 8 | "name": "Cory Leach", 9 | "email": "cory.leach@gmail.com", 10 | "url": "https://github.com/coryleach", 11 | "github": "coryleach", 12 | "twitter": "coryleach" 13 | }, 14 | "unity": "2019.3", 15 | "type": "tool" 16 | } -------------------------------------------------------------------------------- /Tests/com.gameframe.packages.tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.gameframe.packages.tests", 3 | "references": [ 4 | "GUID:8774ef0c0d363ae48b23b3dd950d3560" 5 | ], 6 | "optionalUnityReferences": [ 7 | "TestAssemblies" 8 | ], 9 | "includePlatforms": [], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [] 17 | } -------------------------------------------------------------------------------- /Editor/PackageNpmPublisher/PackageNpmPublisher.uxml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Editor/PackageSettings.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | 3 | namespace Gameframe.Packages.Editor 4 | { 5 | public static class PackageSettings 6 | { 7 | public static string SourcePath 8 | { 9 | get => EditorPrefs.GetString("PackageSourcePath"); 10 | set => EditorPrefs.SetString("PackageSourcePath", value); 11 | } 12 | 13 | private static PackageManifest manifest = new PackageManifest(); 14 | public static PackageManifest Manifest 15 | { 16 | get => manifest ?? (manifest = new PackageManifest()); 17 | set => manifest = value; 18 | } 19 | 20 | public static void Load() 21 | { 22 | 23 | } 24 | 25 | public static void Save() 26 | { 27 | 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Editor/PackageManifest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Gameframe.Packages 4 | { 5 | [Serializable] 6 | public class PackageManifest 7 | { 8 | [Serializable] 9 | public class PackageAuthor 10 | { 11 | public string name = "Cory Leach"; 12 | public string email = "cory.leach@gmail.com"; 13 | public string url = "https://github.com/coryleach"; 14 | public string twitter = "coryleach"; 15 | public string github = "coryleach"; 16 | } 17 | 18 | 19 | public string githubUrl = ""; 20 | public string name = "com.gameframe.mypackagename"; 21 | public string displayName = "My Package Name"; 22 | public string repositoryName = "RepositoryName"; 23 | public string version = "1.0.0"; 24 | public string description = ""; 25 | public string type = "library"; //tool, module, tests, sample, template, library 26 | public string unity = ""; 27 | public string unityRelease = ""; 28 | public string[] keywords = new string[0]; 29 | public PackageAuthor author = new PackageAuthor(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Editor/Utility/AsyncOperationAwaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | using UnityEngine; 5 | 6 | namespace Gameframe.Packages.Utility 7 | { 8 | public class AsyncOperationAwaiter : INotifyCompletion 9 | { 10 | private readonly Func _asyncOperation; 11 | private Action _continuation; 12 | 13 | public AsyncOperationAwaiter(Func asyncOperation) 14 | { 15 | _asyncOperation = asyncOperation; 16 | _continuation = null; 17 | Await(); 18 | } 19 | 20 | public void GetResult() 21 | { 22 | } 23 | 24 | public AsyncOperationAwaiter GetAwaiter() => this; 25 | 26 | public bool IsCompleted => _asyncOperation.Invoke(); 27 | 28 | public void OnCompleted(Action continuation) 29 | { 30 | _continuation = continuation; 31 | } 32 | 33 | private async void Await() 34 | { 35 | while (!IsCompleted) 36 | { 37 | await Task.Yield(); 38 | } 39 | _continuation?.Invoke(); 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 cory.leach@gmail.com 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 | -------------------------------------------------------------------------------- /Editor/PackageGuiUtility.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Gameframe.Packages.Editor 5 | { 6 | public static class PackageGuiUtility 7 | { 8 | /// 9 | /// Draws the UI for and updates packages source path 10 | /// 11 | /// true if the source path location was modified. Otherwise false. 12 | public static bool SourcePathGui() 13 | { 14 | EditorGUILayout.BeginVertical("box"); 15 | EditorGUILayout.LabelField("Source Path"); 16 | EditorGUILayout.BeginHorizontal("box"); 17 | EditorGUILayout.LabelField(PackageSettings.SourcePath); 18 | if (GUILayout.Button("Browse")) 19 | { 20 | PackageSettings.SourcePath = EditorUtility.OpenFolderPanel("Package Source", PackageSettings.SourcePath, "GitHub"); 21 | return true; 22 | } 23 | if (GUILayout.Button("Open")) 24 | { 25 | EditorUtility.RevealInFinder(PackageSettings.SourcePath); 26 | return true; 27 | } 28 | EditorGUILayout.EndHorizontal(); 29 | EditorGUILayout.EndVertical(); 30 | return false; 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Editor/SourcePackageInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using UnityEngine; 4 | 5 | namespace Gameframe.Packages 6 | { 7 | public class SourcePackageInfo 8 | { 9 | public SourcePackageInfo(string path) 10 | { 11 | directoryInfo = new DirectoryInfo(path); 12 | try 13 | { 14 | status = Status.NotChecked; 15 | var packageJson = File.ReadAllText($"{path}/package.json"); 16 | packageInfo = JsonUtility.FromJson(packageJson); 17 | if (packageInfo == null) 18 | { 19 | status = Status.Error; 20 | error = "Failed to get manifest from json"; 21 | } 22 | } 23 | catch (Exception e) 24 | { 25 | status = Status.Error; 26 | error = e.Message; 27 | } 28 | } 29 | 30 | public DirectoryInfo directoryInfo; 31 | 32 | public PackageManifest packageInfo; 33 | 34 | public Status status = Status.NotChecked; 35 | 36 | public string error = string.Empty; 37 | 38 | public enum Status 39 | { 40 | NotChecked, 41 | Error, 42 | NotEmbeded, 43 | Embeded 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Template/Licenses/MIT LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) {DATE.YEAR} {AUTHOR.NAME} 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Never ignore Asset meta data 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # TextMesh Pro files 20 | [Aa]ssets/TextMesh*Pro/ 21 | 22 | # Autogenerated Jetbrains Rider plugin 23 | [Aa]ssets/Plugins/Editor/JetBrains* 24 | 25 | # Visual Studio cache directory 26 | .vs/ 27 | 28 | # Gradle cache directory 29 | .gradle/ 30 | 31 | # Autogenerated VS/MD/Consulo solution and project files 32 | ExportedObj/ 33 | .consulo/ 34 | *.csproj 35 | *.unityproj 36 | *.sln 37 | *.suo 38 | *.tmp 39 | *.user 40 | *.userprefs 41 | *.pidb 42 | *.booproj 43 | *.svd 44 | *.pdb 45 | *.mdb 46 | *.opendb 47 | *.VC.db 48 | 49 | # Unity3D generated meta files 50 | *.pidb.meta 51 | *.pdb.meta 52 | *.mdb.meta 53 | 54 | # Unity3D generated file on crash reports 55 | sysinfo.txt 56 | 57 | # Builds 58 | *.apk 59 | *.unitypackage 60 | 61 | # Crashlytics generated file 62 | crashlytics-build.properties 63 | 64 | -------------------------------------------------------------------------------- /Template/README_TEMPLATE.md: -------------------------------------------------------------------------------- 1 |

{PACKAGE.DISPLAYNAME} 👋

2 |

3 | Version 4 | 5 | Twitter: {TWITTER.USERNAME} 6 | 7 |

8 | 9 | {PACKAGE.DESCRIPTION} 10 | 11 | ## Quick Package Install 12 | 13 | #### Using UnityPackageManager (for Unity 2019.3 or later) 14 | Open the package manager window (menu: Window > Package Manager)
15 | Select "Add package from git URL...", fill in the pop-up with the following link:
16 | {PACKAGE.URL}
17 | 18 | #### Using UnityPackageManager (for Unity 2019.1 or later) 19 | 20 | Find the manifest.json file in the Packages folder of your project and edit it to look like this: 21 | ```js 22 | { 23 | "dependencies": { 24 | "{PACKAGE.NAME}": "{PACKAGE.URL}", 25 | ... 26 | }, 27 | } 28 | ``` 29 | 30 | 31 | 34 | 35 | ## Usage 36 | {PACKAGE.USAGE} 37 | 38 | 39 | 40 | ## Author 41 | 42 | 👤 **{AUTHOR.NAME}** 43 | 44 | {AUTHOR.SOCIAL} 45 | 46 | ## Show your support 47 | 48 | Give a ⭐️ if this project helped you! 49 | 50 | *** 51 | _This README was generated with ❤️ by [Gameframe.Packages](https://github.com/coryleach/unitypackages)_ 52 | -------------------------------------------------------------------------------- /Editor/PackageMenu.cs: -------------------------------------------------------------------------------- 1 | 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Gameframe.Packages.Editor 6 | { 7 | 8 | public static class PackageMenu 9 | { 10 | [MenuItem("Gameframe/Packages/Create")] 11 | public static void CreateWindow() 12 | { 13 | var window = (PackageMaintainerWindow)EditorWindow.GetWindow(typeof(PackageMaintainerWindow), false, "Package Maintainer"); 14 | window.autoRepaintOnSceneChange = true; 15 | window.tab = 2; 16 | } 17 | 18 | [MenuItem("Gameframe/Packages/Maintain")] 19 | public static void MaintainWindow() 20 | { 21 | var window = (PackageMaintainerWindow)EditorWindow.GetWindow(typeof(PackageMaintainerWindow), false, "Package Maintainer"); 22 | window.autoRepaintOnSceneChange = true; 23 | window.tab = 0; 24 | } 25 | 26 | [MenuItem("Gameframe/Packages/Embed")] 27 | public static void EmbedWindow() 28 | { 29 | var window = (PackageMaintainerWindow)EditorWindow.GetWindow(typeof(PackageMaintainerWindow), false, "Package Maintainer"); 30 | window.autoRepaintOnSceneChange = true; 31 | window.tab = 1; 32 | } 33 | 34 | [MenuItem( "Gameframe/Packages/Documentation/Custom Packages" )] 35 | public static void DocumentationCustomPackages() 36 | { 37 | Application.OpenURL("https://docs.unity3d.com/Manual/CustomPackages.html"); 38 | } 39 | 40 | [MenuItem("Gameframe/Packages/Documentation/Readme Markdown")] 41 | public static void ReadmeMarkdown() 42 | { 43 | Application.OpenURL("https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax"); 44 | } 45 | 46 | 47 | [MenuItem("Gameframe/Packages/Documentation/Layout Convention")] 48 | public static void DocumentationPackageLayout() 49 | { 50 | Application.OpenURL("https://docs.unity3d.com/Manual/cus-layout.html"); 51 | } 52 | 53 | [MenuItem( "Gameframe/Packages/Documentation/Package Manifest" )] 54 | public static void DocumentationPackageManifest() 55 | { 56 | Application.OpenURL("https://docs.unity3d.com/Manual/upm-manifestPkg.html"); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Gameframe.Packages 👋

2 |

3 | Version 4 | 5 | Twitter: coryleach 6 | 7 |

8 | 9 | Package for creating Unity packages just like this one! 10 | 11 | ## Quick Package Install 12 | 13 | #### Using UnityPackageManager (for Unity 2019.3 or later) 14 | Open the package manager window (menu: Window > Package Manager)
15 | Select "Add package from git URL...", fill in the pop-up with the following link:
16 | https://github.com/coryleach/UnityPackages.git#1.0.2
17 | 18 | #### Using UnityPackageManager (for Unity 2019.1 or later) 19 | 20 | Find the manifest.json file in the Packages folder of your project and edit it to look like this: 21 | ```js 22 | { 23 | "dependencies": { 24 | "com.gameframe.packages": "https://github.com/coryleach/UnityPackages.git#1.0.2", 25 | ... 26 | }, 27 | } 28 | ``` 29 | 30 | 31 | 34 | 35 | ## Usage 36 | 37 | Open the window using the gameframe menu. 38 | 39 | Gameframe->Packages->Maintain 40 | The maintain tab displays and allows you to edit package manifest details 41 | It also has a button for updating the README file. 42 | 43 | Gameframe->Packages->Create 44 | The create tab is used for creating new packages. 45 | You can create packages either embeded in the Unity project or in the chosen source directory. 46 | 47 | Gameframe->Packages->Embed 48 | The embed tap will scan the source directory for packages. 49 | Clicking the 'embed' button on a package will create a softlink to the package in the project's Packages folder. 50 | 51 | 52 | 53 | ## Author 54 | 55 | 👤 **Cory Leach** 56 | 57 | * Twitter: [@coryleach](https://twitter.com/coryleach) 58 | * Github: [@coryleach](https://github.com/coryleach) 59 | 60 | 61 | ## Show your support 62 | 63 | Give a ⭐️ if this project helped you! 64 | 65 | *** 66 | _This README was generated with ❤️ by [Gameframe.Packages](https://github.com/coryleach/unitypackages)_ 67 | -------------------------------------------------------------------------------- /Tests/ShellUtilityTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using NUnit.Framework; 3 | using UnityEngine.TestTools; 4 | 5 | namespace Gameframe.Shell.Tests 6 | { 7 | public class ShellUtilityTests 8 | { 9 | // A Test behaves as an ordinary method 10 | [Test] 11 | public void ExecuteCommand_UseShell([Values(true,false)] bool useShell) 12 | { 13 | var command = "echo \"hello\""; 14 | var result = ShellUtility.ExecuteCommand(command,useShell); 15 | Assert.IsTrue(result,$"Failed to execute command: {command}"); 16 | } 17 | 18 | [Test] 19 | public void GetCommandResult_TrimOutput([Values(true,false)] bool trimOutput) 20 | { 21 | var echoText = "hello"; 22 | var command = $"echo \"{echoText}\""; 23 | var result = ShellUtility.GetCommandResult(command,trimOutput); 24 | 25 | if (trimOutput) 26 | { 27 | Assert.IsTrue(result == echoText,$"Failed to execute command and get result: {echoText} != {result}"); 28 | } 29 | else 30 | { 31 | Assert.IsTrue(result != echoText,$"Failed to execute command and get result: {echoText} != {result}"); 32 | Assert.IsTrue(result.StartsWith(echoText),$"Failed to execute command and get result: {echoText} != {result}"); 33 | } 34 | } 35 | 36 | [UnityTest] 37 | public IEnumerator ExecuteCommandAsync_UseShell([Values(true,false)] bool useShell) 38 | { 39 | var command = "echo \"hello\""; 40 | var task = ShellUtility.ExecuteCommandAsync(command,useShell); 41 | 42 | while (!task.IsCompleted) 43 | { 44 | yield return null; 45 | } 46 | 47 | var result = task.Result; 48 | Assert.IsTrue(result,$"Failed to execute command: {command}"); 49 | } 50 | 51 | [UnityTest] 52 | public IEnumerator GetCommandResultAsync_TrimOutput([Values(true,false)] bool trimOutput) 53 | { 54 | var echoText = "hello"; 55 | var command = $"echo \"{echoText}\""; 56 | var task = ShellUtility.GetCommandResultAsync(command,trimOutput); 57 | 58 | while (!task.IsCompleted) 59 | { 60 | yield return null; 61 | } 62 | 63 | var result = task.Result; 64 | 65 | if (trimOutput) 66 | { 67 | Assert.IsTrue(result == echoText,$"Failed to execute command and get result: {echoText} != {result}"); 68 | } 69 | else 70 | { 71 | Assert.IsTrue(result != echoText,$"Failed to execute command and get result: {echoText} != {result}"); 72 | Assert.IsTrue(result.StartsWith(echoText),$"Failed to execute command and get result: {echoText} != {result}"); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Documentation/GameframeFace.gif.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c95fc43cb07b4db9befe7e733e5e938 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 1 38 | mipBias: 0 39 | wrapU: 0 40 | wrapV: 0 41 | wrapW: 0 42 | nPOTScale: 1 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 0 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 0 55 | spriteTessellationDetail: -1 56 | textureType: 0 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 0 66 | cookieLightType: 0 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 2048 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 0 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 2048 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 0 92 | - serializedVersion: 3 93 | buildTarget: Server 94 | maxTextureSize: 2048 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 0 104 | spriteSheet: 105 | serializedVersion: 2 106 | sprites: [] 107 | outline: [] 108 | physicsShape: [] 109 | bones: [] 110 | spriteID: 111 | internalID: 0 112 | vertices: [] 113 | indices: 114 | edges: [] 115 | weights: [] 116 | secondaryTextures: [] 117 | nameFileIdTable: {} 118 | spritePackingTag: 119 | pSDRemoveMatte: 0 120 | pSDShowRemoveMatteOption: 0 121 | userData: 122 | assetBundleName: 123 | assetBundleVariant: 124 | -------------------------------------------------------------------------------- /Editor/PackageUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Gameframe.Packages 5 | { 6 | public static class PackageUtility 7 | { 8 | private const string StartDocTag = ""; 9 | private const string EndDocTag = ""; 10 | 11 | public static string ExtractText(string text) 12 | { 13 | var startIndex = text.IndexOf(StartDocTag, StringComparison.Ordinal) + StartDocTag.Length; 14 | var endIndex = text.IndexOf(EndDocTag, startIndex, StringComparison.Ordinal); 15 | 16 | if (startIndex == -1 || endIndex == -1) 17 | { 18 | return string.Empty; 19 | } 20 | 21 | return text.Substring(startIndex, endIndex - startIndex); 22 | } 23 | 24 | public static string PatchReadmeText(string oldReadmeText, string templateText) 25 | { 26 | //Extract Documenation from old readme 27 | oldReadmeText = ExtractText(oldReadmeText); 28 | //Find the replace-able text in the template 29 | var replaceableText = ExtractText(templateText); 30 | //Insert old readme text into the template 31 | return templateText.Replace(replaceableText, oldReadmeText); 32 | } 33 | 34 | public static string CreateLicenseText(string licenseText, PackageManifest packageManifest) 35 | { 36 | var licenseBuilder = new StringBuilder(licenseText); 37 | licenseBuilder.Replace("{DATE.YEAR}",DateTime.Now.Year.ToString()); 38 | licenseBuilder.Replace("{AUTHOR.NAME}",packageManifest.author.name); 39 | return licenseBuilder.ToString(); 40 | } 41 | 42 | public static string GithubUrl(string username) 43 | { 44 | return $"https://github.com/{username}"; 45 | } 46 | 47 | public static string TwitterUrl(string username) 48 | { 49 | return $"https://twitter.com/{username}"; 50 | } 51 | 52 | public static string PackageUrl(string github, string repository, string version) 53 | { 54 | return $"https://github.com/{github}/{repository}.git#{version}"; 55 | } 56 | 57 | public static string CreateReadmeText(string text, PackageManifest packageManifest) 58 | { 59 | var description = packageManifest.description; 60 | 61 | //Replace line endings so that text in the readme line breaks properly 62 | description = description.Replace("\r\n", "\n"); 63 | description = description.Replace("\n", " \n"); 64 | 65 | var readmeText = new StringBuilder(text); 66 | readmeText.Replace("{TWITTER.USERNAME}",packageManifest.author.twitter); 67 | readmeText.Replace("{AUTHOR.TWITTER}",packageManifest.author.name); 68 | readmeText.Replace("{AUTHOR.NAME}",packageManifest.author.name); 69 | readmeText.Replace("{GITHUB.USERNAME}",packageManifest.author.github); 70 | readmeText.Replace("{PACKAGE.VERSION}",packageManifest.version); 71 | readmeText.Replace("{PACKAGE.DESCRIPTION}",description); 72 | readmeText.Replace("{PACKAGE.DISPLAYNAME}",packageManifest.displayName); 73 | readmeText.Replace("{PACKAGE.NAME}",packageManifest.name); 74 | readmeText.Replace("{PACKAGE.USAGE}","TODO: Write Usage Documentation Here"); 75 | readmeText.Replace("{PACKAGE.URL}", PackageUrl(packageManifest.author.github, packageManifest.repositoryName, packageManifest.version)); 76 | 77 | var social = new StringBuilder(); 78 | if (!string.IsNullOrEmpty(packageManifest.author.twitter)) 79 | { 80 | social.AppendLine($"* Twitter: [@{packageManifest.author.twitter}]({TwitterUrl(packageManifest.author.twitter)})"); 81 | } 82 | if (!string.IsNullOrEmpty(packageManifest.author.github)) 83 | { 84 | social.AppendLine($"* Github: [@{packageManifest.author.github}]({GithubUrl(packageManifest.author.github)})"); 85 | } 86 | readmeText.Replace("{AUTHOR.SOCIAL}", social.ToString()); 87 | 88 | return readmeText.ToString(); 89 | } 90 | 91 | } 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /Editor/ShellUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | 5 | namespace Gameframe.Shell 6 | { 7 | public static class ShellUtility 8 | { 9 | /// 10 | /// Executes a command in the system shell. 11 | /// Windows uses: powershell.exe by default. Use ExecuteWindowsCommand for cmd.exe 12 | /// Mac uses: /bin/bash 13 | /// 14 | /// Command to execute 15 | /// If true we'll use the OS shell. Otherwise command will execute as its own process. 16 | /// True if command exited with code 0 otherwise returns false 17 | public static bool ExecuteCommand(string command, bool useShell = false) 18 | { 19 | #if UNITY_EDITOR_WIN 20 | var commandBytes = System.Text.Encoding.Unicode.GetBytes(command); 21 | var encodedCommand = Convert.ToBase64String(commandBytes); 22 | var processInfo = new ProcessStartInfo("powershell.exe", $"-EncodedCommand {encodedCommand}") 23 | { 24 | CreateNoWindow = true, 25 | UseShellExecute = useShell, 26 | RedirectStandardOutput = !useShell, 27 | WindowStyle = ProcessWindowStyle.Hidden 28 | }; 29 | #else 30 | var processInfo = new ProcessStartInfo("/bin/bash", $"-c \"{command.Replace("\\","\\\\")}\"") 31 | { 32 | CreateNoWindow = true, 33 | UseShellExecute = useShell, 34 | RedirectStandardOutput = !useShell, 35 | WindowStyle = ProcessWindowStyle.Hidden 36 | }; 37 | #endif 38 | 39 | var process = Process.Start(processInfo); 40 | if (process == null) 41 | { 42 | return false; 43 | } 44 | 45 | process.WaitForExit(); 46 | var exitCode = process.ExitCode; 47 | process.Close(); 48 | 49 | return exitCode == 0; 50 | } 51 | 52 | /// 53 | /// Execute command using the basic windows shell cmd.exe 54 | /// 55 | /// command to be executed 56 | /// true if successfull. false otherwise. 57 | public static bool ExecuteWindowsCommand(string command) 58 | { 59 | command = $"/c {command}"; 60 | var processInfo = new ProcessStartInfo("cmd.exe", command.Replace("\\","\\\\")) 61 | { 62 | CreateNoWindow = true, 63 | Verb = "runas", 64 | WindowStyle = ProcessWindowStyle.Hidden 65 | }; 66 | 67 | var process = Process.Start(processInfo); 68 | if (process == null) 69 | { 70 | return false; 71 | } 72 | 73 | process.WaitForExit(); 74 | var exitCode = process.ExitCode; 75 | process.Close(); 76 | 77 | return exitCode == 0; 78 | } 79 | 80 | /// 81 | /// Get the string output of a command in the shell 82 | /// 83 | /// Command string to be run 84 | /// True if we should trim new line and line feed from end of output stream 85 | /// String output of the command. 86 | public static string GetCommandResult(string command, bool trimOutput = true) 87 | { 88 | #if UNITY_EDITOR_WIN 89 | var commandBytes = System.Text.Encoding.Unicode.GetBytes(command); 90 | var encodedCommand = Convert.ToBase64String(commandBytes); 91 | var processInfo = new ProcessStartInfo("powershell.exe", $"-EncodedCommand {encodedCommand}") 92 | { 93 | CreateNoWindow = true, 94 | UseShellExecute = false, 95 | RedirectStandardOutput = true 96 | }; 97 | #else 98 | var processInfo = new ProcessStartInfo("/bin/bash", $"-c \"{command.Replace("\\","\\\\")}\"") 99 | { 100 | CreateNoWindow = true, 101 | UseShellExecute = false, 102 | RedirectStandardOutput = true, 103 | WindowStyle = ProcessWindowStyle.Hidden 104 | }; 105 | #endif 106 | 107 | var process = Process.Start(processInfo); 108 | if (process == null) 109 | { 110 | return null; 111 | } 112 | 113 | var output = process.StandardOutput.ReadToEnd(); 114 | process.WaitForExit(); 115 | 116 | var exitCode = process.ExitCode; 117 | process.Close(); 118 | 119 | if (exitCode != 0) 120 | { 121 | return null; 122 | } 123 | 124 | if (trimOutput) 125 | { 126 | output = output.TrimEnd('\n','\r'); 127 | } 128 | 129 | return output; 130 | } 131 | 132 | /// 133 | /// Run a command and get the string output as a task 134 | /// 135 | /// Command string to run. 136 | /// Trims any new line characters from the end of the output 137 | /// Task that results in a string containing the output of the command. OR null if command failed to execute. 138 | public static async Task GetCommandResultAsync(string command, bool trimOutput = true) 139 | { 140 | var task = Task.Run(() => GetCommandResult(command,trimOutput)); 141 | await task; 142 | return task.Result; 143 | } 144 | 145 | /// 146 | /// Execute a shell command as a task 147 | /// 148 | /// command string to execute 149 | /// If true the command will execute in a system shell. 150 | /// Task with a result of true when command exits with code 0. 151 | public static async Task ExecuteCommandAsync(string command, bool useShell = false) 152 | { 153 | var task = Task.Run(() => ExecuteCommand(command, useShell)); 154 | await task; 155 | return task.Result; 156 | } 157 | 158 | /// 159 | /// Executes a command to create a symbolic link 160 | /// 161 | /// Source file path 162 | /// Destination file path 163 | /// set this to true if you're creating a link for a directory 164 | /// true if the command is a success. false on failure. 165 | public static bool CreateSymbolicLink(string source, string destination) 166 | { 167 | #if UNITY_EDITOR_WIN 168 | return ExecuteWindowsCommand($"mklink /D \"{destination}\" \"{source}\""); 169 | #else 170 | return ExecuteCommand($"ln -s {source} {destination}"); 171 | #endif 172 | } 173 | 174 | } 175 | } -------------------------------------------------------------------------------- /Editor/PackageNpmPublisher/PackageNpmPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Gameframe.Shell; 5 | using UnityEditor; 6 | using UnityEditor.UIElements; 7 | using UnityEngine; 8 | using UnityEngine.UIElements; 9 | using Debug = UnityEngine.Debug; 10 | 11 | namespace Gameframe.Packages.Editor 12 | { 13 | public class PackageNpmPublisher : EditorWindow 14 | { 15 | public string address = "npm.coryleach.info:4873"; 16 | public string username = "coryleach"; 17 | public string email = "cory.leach@gmail.com"; 18 | public string password = ""; 19 | 20 | private List selectedPackageList = new List(); 21 | private ScrollView packageScrollList; 22 | private Label loginStatusLabel; 23 | 24 | private static string ResourcePath = "Packages/com.gameframe.packages/Editor/PackagePublisherWindow/"; 25 | 26 | private InstallStatus installStatus = InstallStatus.Unknown; 27 | 28 | private const string StyleSheetFilename = "PackageNpmPublisher.uss"; 29 | 30 | public enum InstallStatus 31 | { 32 | Unknown, 33 | Installed, 34 | NotFound 35 | } 36 | 37 | //Commenting this out. 38 | //I have no plans to continue to support this for now 39 | /*[MenuItem("Gameframe/Packages/Publisher")] 40 | public static void Open() 41 | { 42 | PackageNpmPublisher wnd = GetWindow(); 43 | wnd.titleContent = new GUIContent("Package Publisher"); 44 | }*/ 45 | 46 | public void OnEnable() 47 | { 48 | // Each editor window contains a root VisualElement object 49 | VisualElement root = rootVisualElement; 50 | 51 | // A stylesheet can be added to a VisualElement. 52 | // The style will be applied to the VisualElement and all of its children. 53 | var styleSheetPath = ResourcePath + StyleSheetFilename; 54 | var styleSheet = AssetDatabase.LoadAssetAtPath(styleSheetPath); 55 | if ( styleSheet != null ) 56 | { 57 | root.styleSheets.Add(styleSheet); 58 | } 59 | else 60 | { 61 | Debug.LogError($"Failed to Load Style Sheet. Check uss file exists or syntax error."); 62 | } 63 | 64 | // Import UXML 65 | //var visualTree = AssetDatabase.LoadAssetAtPath("Packages/com.gameframe.packages/Editor/PackagePublisherWindow/PackagePublisher.uxml"); 66 | //VisualElement labelFromUXML = visualTree.CloneTree(); 67 | //root.Add(labelFromUXML); 68 | SerializedObject so = new SerializedObject(this); 69 | 70 | var fieldContainer = new VisualElement() 71 | { 72 | name = "FieldContainer" 73 | }; 74 | fieldContainer.AddToClassList("BorderedContainer"); 75 | root.Add(fieldContainer); 76 | 77 | var serverField = new TextField 78 | { 79 | label = "Server", 80 | bindingPath = nameof(address) 81 | }; 82 | serverField.Bind(so); 83 | fieldContainer.Add(serverField); 84 | 85 | var emailField = new TextField 86 | { 87 | label = "email", 88 | bindingPath = nameof(email) 89 | }; 90 | emailField.Bind(so); 91 | fieldContainer.Add(emailField); 92 | 93 | var usernameField = new TextField 94 | { 95 | label = "Username", 96 | bindingPath = nameof(username), 97 | }; 98 | usernameField.Bind(so); 99 | fieldContainer.Add(usernameField); 100 | 101 | var passwordField = new TextField() 102 | { 103 | label = "Password", 104 | bindingPath = nameof(password), 105 | isPasswordField = true 106 | }; 107 | passwordField.Bind(so); 108 | fieldContainer.Add(passwordField); 109 | 110 | packageScrollList = new ScrollView(); 111 | packageScrollList.AddToClassList("BorderedContainer"); 112 | root.Add(packageScrollList); 113 | 114 | var buttonContainer = new VisualElement() 115 | { 116 | name = "ButtonContainer", 117 | style = 118 | { 119 | flexDirection = FlexDirection.Row, 120 | justifyContent = Justify.Center, 121 | } 122 | }; 123 | root.Add(buttonContainer); 124 | 125 | loginStatusLabel = new Label("Waiting...") 126 | { 127 | style = 128 | { 129 | unityTextAlign = TextAnchor.MiddleRight 130 | } 131 | }; 132 | fieldContainer.Add(loginStatusLabel); 133 | 134 | var loginButton = new Button(Login) 135 | { 136 | name = "ButtonLogin", 137 | text = "Login", 138 | style = 139 | { 140 | marginTop = 5, 141 | marginLeft = 5, 142 | marginRight = 5, 143 | paddingTop = 4 144 | } 145 | }; 146 | fieldContainer.Add(loginButton); 147 | 148 | var publishButton = new Button(Publish) 149 | { 150 | name = "ButtonPublish", 151 | text = "Publish", 152 | style = 153 | { 154 | marginLeft = 5, 155 | marginRight = 5 156 | } 157 | }; 158 | buttonContainer.Add(publishButton); 159 | 160 | var refreshButton = new Button(Refresh) 161 | { 162 | name = "ButtonRefresh", 163 | text = "Refresh", 164 | style = 165 | { 166 | marginLeft = 5, 167 | marginRight = 5 168 | } 169 | }; 170 | buttonContainer.Add(refreshButton); 171 | 172 | /*var installButton = new Button(InstallCli) 173 | { 174 | name = "ButtonInstall", 175 | style = 176 | { 177 | marginLeft = 5, 178 | marginRight = 5 179 | } 180 | }; 181 | installButton.Add(new Label("Install")); 182 | buttonContainer.Add(installButton);*/ 183 | 184 | selectedPackageList = new List(); 185 | PopulateScrollViewWithPackages(packageScrollList); 186 | 187 | CheckLogin(); 188 | CheckInstall(); 189 | } 190 | 191 | private async void CheckInstall() 192 | { 193 | installStatus = InstallStatus.Unknown; 194 | var checkTask = await ShellUtility.ExecuteCommandAsync("npm version"); 195 | installStatus = !checkTask ? InstallStatus.NotFound : InstallStatus.Installed; 196 | } 197 | 198 | private async void CheckLogin() 199 | { 200 | loginStatusLabel.text = "Waiting..."; 201 | var checkTask = ShellUtility.ExecuteCommandAsync("npm whoami"); 202 | await checkTask; 203 | loginStatusLabel.text = !checkTask.Result ? "Disconnected" : "Connected"; 204 | } 205 | 206 | /*private void InstallCli() 207 | { 208 | Debug.Log("Installing..."); 209 | ExecuteShellCommand("npm install -g npm-cli-login publish"); 210 | } 211 | 212 | private void UpdateVersions() 213 | { 214 | var directories = Directory.GetDirectories("Packages/"); 215 | foreach (var directory in directories) 216 | { 217 | var filename = $"{directory}/package.json"; 218 | var json = File.ReadAllText(filename); 219 | 220 | var match = Regex.Match(json, @"""version"": ""(\d+\.)(\d+\.)(\d+)"""); 221 | match = Regex.Match(match.Value, @"(\d+\.)(\d+\.)(\d+)"); 222 | var split = match.Value.Split('.'); 223 | var buildNumber = int.Parse(split[split.Length - 1]); 224 | buildNumber += 1; 225 | 226 | var newVersion = $"\"version\": \"{split[0]}.{split[1]}.{buildNumber}\""; 227 | var updatedJson = Regex.Replace(json, @"""version"": ""(\d+\.)(\d+\.)(\d+)""", newVersion); 228 | 229 | File.WriteAllText(filename, updatedJson); 230 | } 231 | }*/ 232 | 233 | #region Commands 234 | 235 | private void Refresh() 236 | { 237 | packageScrollList.Clear(); 238 | PopulateScrollViewWithPackages(packageScrollList); 239 | } 240 | 241 | private async void Publish() 242 | { 243 | if (selectedPackageList.Count == 0) 244 | { 245 | Debug.Log("No selected packages to publish"); 246 | return; 247 | } 248 | 249 | foreach (var package in selectedPackageList) 250 | { 251 | //publishing 252 | Debug.Log($"Publishing {package.displayName} {package.version}"); 253 | var cmd = $"npm publish Packages/{package.name} --registry http://{address}"; 254 | var task = ShellUtility.ExecuteCommandAsync(cmd); 255 | await task; 256 | if (!task.Result) 257 | { 258 | Debug.LogError($"Failed to publish {package.displayName}"); 259 | } 260 | else 261 | { 262 | Debug.Log($"Published {package.displayName}"); 263 | } 264 | } 265 | 266 | Refresh(); 267 | } 268 | 269 | private async void Login() 270 | { 271 | loginStatusLabel.text = "Waiting..."; 272 | 273 | var cmd = $"npm-cli-login -u {username} -p {password} -e {email} -r http://{address}"; 274 | var task = ShellUtility.ExecuteCommandAsync(cmd); 275 | await task; 276 | if (!task.Result) 277 | { 278 | Debug.Log("Failed login"); 279 | } 280 | 281 | cmd = $"npm config set registry http://{address}"; 282 | task = ShellUtility.ExecuteCommandAsync(cmd); 283 | await task; 284 | if (!task.Result) 285 | { 286 | Debug.Log("Failed to set registry address"); 287 | } 288 | 289 | CheckLogin(); 290 | } 291 | 292 | #endregion 293 | 294 | private static List GetLocalPackageList() 295 | { 296 | var packageList = new List(); 297 | var directories = Directory.GetDirectories("Packages/"); 298 | foreach (var directory in directories) 299 | { 300 | var packageManifestPath = $"{directory}/package.json"; 301 | //publishing 302 | if (!File.Exists(packageManifestPath)) 303 | { 304 | continue; 305 | } 306 | 307 | var json = File.ReadAllText(packageManifestPath); 308 | var packageManifest = JsonUtility.FromJson(json); 309 | if (packageManifest != null) 310 | { 311 | packageList.Add(packageManifest); 312 | } 313 | } 314 | return packageList; 315 | } 316 | 317 | private async Task> GetLocalPackageListAsync() 318 | { 319 | var task = Task.Run(GetLocalPackageListAsync); 320 | await task; 321 | return task.Result; 322 | } 323 | 324 | private async void PopulateScrollViewWithPackages(ScrollView scrollView) 325 | { 326 | var packages = GetLocalPackageListAsync(); 327 | await packages; 328 | int row = 0; 329 | foreach (var package in packages.Result) 330 | { 331 | var currentPackage = package; 332 | var toggle = new Toggle 333 | { 334 | label = $"{currentPackage.displayName}:{currentPackage.version}", 335 | }; 336 | toggle.AddToClassList(row % 2 == 0 ? "rowEven" : "rowOdd"); 337 | toggle.labelElement.style.flexGrow = 100; 338 | toggle.RegisterValueChangedCallback((value) => 339 | { 340 | if (value.newValue) 341 | { 342 | selectedPackageList.Add(currentPackage); 343 | } 344 | else 345 | { 346 | selectedPackageList.Remove(currentPackage); 347 | } 348 | }); 349 | scrollView.Add(toggle); 350 | row += 1; 351 | 352 | SetToggleRemoteVersionAsync(toggle,package); 353 | } 354 | } 355 | 356 | private static async void SetToggleRemoteVersionAsync(Toggle toggle, PackageManifest package) 357 | { 358 | PackageManifest remotePackage = null; 359 | 360 | var task = ShellUtility.GetCommandResultAsync($"npm view {package.name} --json"); 361 | await task; 362 | var json = task.Result; 363 | 364 | if (json != null) 365 | { 366 | remotePackage = JsonUtility.FromJson(json); 367 | } 368 | 369 | toggle.label = remotePackage == null ? $"{toggle.label} (not found)" : $"{toggle.label} ({remotePackage.version})"; 370 | toggle.SetEnabled(remotePackage == null || remotePackage.version != package.version); 371 | toggle.value = false; 372 | } 373 | 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /Editor/PackageMaintainerWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Gameframe.Packages.Editor; 7 | using Gameframe.Packages.Utility; 8 | using Gameframe.Shell; 9 | using UnityEditor; 10 | using UnityEditor.PackageManager; 11 | using UnityEngine; 12 | using PackageInfo = UnityEditor.PackageManager.PackageInfo; 13 | 14 | namespace Gameframe.Packages 15 | { 16 | public class PackageMaintainerWindow : EditorWindow 17 | { 18 | 19 | public PackageManifest packageManifest = new PackageManifest(); 20 | public List embededPackages = new List(); 21 | public int selectedPackageIndex = 0; 22 | public string[] packageNames = new string[0]; 23 | public List sourcePackages = new List(); 24 | public int tab = 0; 25 | public bool displayName = true; 26 | 27 | private ScriptableObject target = null; 28 | private SerializedObject serializedObject = null; 29 | private Vector2 scrollPt = Vector2.zero; 30 | private readonly string[] toolbar = {"Maintain", "Embed", "Create"}; 31 | 32 | private void OnEnable() 33 | { 34 | target = this; 35 | serializedObject = new SerializedObject(target); 36 | packageManifest = PackageSettings.Manifest; 37 | Refresh(); 38 | } 39 | 40 | private void OnProjectChange() 41 | { 42 | Refresh(); 43 | } 44 | 45 | private void UpdateSourcePackages() 46 | { 47 | var sourcePath = PackageSettings.SourcePath; 48 | if (string.IsNullOrEmpty(sourcePath)) 49 | { 50 | return; 51 | } 52 | 53 | sourcePackages.Clear(); 54 | 55 | var directories = Directory.GetDirectories(sourcePath); 56 | foreach (var directory in directories) 57 | { 58 | //Check each directory for a package manifest 59 | if (File.Exists($"{directory}/package.json")) 60 | { 61 | var pkgInfo = new SourcePackageInfo(directory); 62 | sourcePackages.Add(pkgInfo); 63 | } 64 | } 65 | 66 | SortPackageList(); 67 | CheckEmbededStatus(); 68 | } 69 | 70 | private void CheckEmbededStatus() 71 | { 72 | foreach (var sourcePackage in sourcePackages) 73 | { 74 | if (sourcePackage.status == SourcePackageInfo.Status.Error) 75 | { 76 | continue; 77 | } 78 | 79 | sourcePackage.status = embededPackages.Any(x => x.name == sourcePackage.packageInfo.name) 80 | ? SourcePackageInfo.Status.Embeded 81 | : SourcePackageInfo.Status.NotEmbeded; 82 | } 83 | } 84 | 85 | private async void UpdateEmbeddedPackages() 86 | { 87 | var request = Client.List(); 88 | 89 | await request; 90 | 91 | if (request.Status == StatusCode.InProgress) 92 | { 93 | Debug.LogError("Failed to await package list response."); 94 | return; 95 | } 96 | 97 | if (request.Status == StatusCode.Failure) 98 | { 99 | Debug.LogError($"Get Packages Failed: {request.Error.errorCode} {request.Error.message}"); 100 | return; 101 | } 102 | 103 | //Get All Embeded Packages 104 | embededPackages = request.Result.Where(x => x.source == PackageSource.Embedded).ToList(); 105 | packageNames = embededPackages.Select(x => x.displayName).ToArray(); 106 | CheckEmbededStatus(); 107 | EditorUtility.SetDirty(this); 108 | } 109 | 110 | private async Task GetMyPackageInfoAsync() 111 | { 112 | var request = Client.List(); 113 | await request; 114 | var result = request.Result.First(x => x.name == "com.gameframe.packages"); 115 | return result; 116 | } 117 | 118 | private void Refresh() 119 | { 120 | UpdateEmbeddedPackages(); 121 | UpdateSourcePackages(); 122 | } 123 | 124 | private void OnGUI() 125 | { 126 | serializedObject.Update(); 127 | tab = GUILayout.Toolbar(tab, toolbar); 128 | switch (tab) 129 | { 130 | case 0: 131 | MaintainPackageGUI(); 132 | break; 133 | case 1: 134 | EmbedPackageGUI(); 135 | break; 136 | case 2: 137 | CreatePackageGUI(); 138 | break; 139 | } 140 | serializedObject.ApplyModifiedProperties(); 141 | } 142 | 143 | private void SortPackageList() 144 | { 145 | if (displayName) 146 | { 147 | sourcePackages.Sort((a,b)=>string.Compare(a.packageInfo.displayName, b.packageInfo.displayName, StringComparison.Ordinal)); 148 | } 149 | else 150 | { 151 | sourcePackages.Sort((a,b)=>string.Compare(a.packageInfo.name, b.packageInfo.name, StringComparison.Ordinal)); 152 | } 153 | } 154 | 155 | private void EmbedPackageGUI() 156 | { 157 | if (PackageGuiUtility.SourcePathGui()) 158 | { 159 | Refresh(); 160 | } 161 | 162 | EditorGUI.BeginChangeCheck(); 163 | displayName = EditorGUILayout.Toggle("ShowDisplayName", displayName); 164 | if (EditorGUI.EndChangeCheck()) 165 | { 166 | SortPackageList(); 167 | } 168 | 169 | bool refreshAssets = false; 170 | 171 | scrollPt = EditorGUILayout.BeginScrollView(scrollPt, GUILayout.ExpandHeight(false)); 172 | foreach (var sourcePkg in sourcePackages) 173 | { 174 | EditorGUILayout.BeginHorizontal("box"); 175 | 176 | if (displayName) 177 | { 178 | EditorGUILayout.LabelField(sourcePkg.packageInfo?.displayName); 179 | } 180 | else 181 | { 182 | EditorGUILayout.LabelField(sourcePkg.packageInfo?.name); 183 | } 184 | 185 | if (sourcePkg.status == SourcePackageInfo.Status.Error) 186 | { 187 | EditorGUILayout.LabelField("Error", GUILayout.Width(60)); 188 | } 189 | else if (sourcePkg.status == SourcePackageInfo.Status.Embeded) 190 | { 191 | EditorGUILayout.LabelField("Embeded", GUILayout.Width(60)); 192 | } 193 | else if (GUILayout.Button("Embed", GUILayout.Width(60))) 194 | { 195 | try 196 | { 197 | //Create a softlink to the source package in our local package directory 198 | var source = sourcePkg.directoryInfo.FullName; 199 | var dest = $"{Application.dataPath}/../Packages/{sourcePkg.directoryInfo.Name}"; 200 | if (!ShellUtility.CreateSymbolicLink(source, dest)) 201 | { 202 | EditorApplication.Beep(); 203 | EditorUtility.DisplayDialog("Package Embed", "Failed to create symbolic link. Package was not embedded.", 204 | "OK"); 205 | } 206 | else 207 | { 208 | refreshAssets = true; 209 | EditorUtility.DisplayDialog("Package Embed", "Done", 210 | "OK"); 211 | } 212 | } 213 | catch ( Exception e ) 214 | { 215 | Debug.LogException(e); 216 | } 217 | } 218 | EditorGUILayout.EndHorizontal(); 219 | } 220 | EditorGUILayout.EndScrollView(); 221 | 222 | //Doing this outside of the foreach loop to avoid layout and enum errors 223 | if (refreshAssets) 224 | { 225 | AssetDatabase.Refresh(); 226 | GUIUtility.ExitGUI(); 227 | } 228 | 229 | RefreshGUI(); 230 | } 231 | 232 | private PackageManifest maintainPackageManifest = null; 233 | 234 | private void MaintainPackageGUI() 235 | { 236 | EditorGUI.BeginChangeCheck(); 237 | 238 | selectedPackageIndex = EditorGUILayout.Popup("Package", selectedPackageIndex, packageNames); 239 | if (EditorGUI.EndChangeCheck()) 240 | { 241 | //Index Changed 242 | maintainPackageManifest = null; 243 | } 244 | 245 | //Validate index 246 | if (selectedPackageIndex < 0 || selectedPackageIndex >= embededPackages.Count) 247 | { 248 | return; 249 | } 250 | 251 | var package = embededPackages[selectedPackageIndex]; 252 | 253 | //Need to get the json for the package 254 | if (maintainPackageManifest == null) 255 | { 256 | var json = File.ReadAllText($"{package.assetPath}/package.json"); 257 | maintainPackageManifest = JsonUtility.FromJson(json); 258 | } 259 | 260 | var rect = EditorGUILayout.BeginVertical("box"); 261 | EditorGUILayout.LabelField("Name",package.name); 262 | EditorGUILayout.LabelField("DisplayName",package.displayName); 263 | EditorGUILayout.LabelField("Source",package.source.ToString()); 264 | EditorGUILayout.LabelField("Asset Path",package.assetPath); 265 | EditorGUILayout.LabelField("Resolved Path",package.resolvedPath); 266 | EditorGUILayout.LabelField("Type",package.type); 267 | EditorGUILayout.LabelField("Version",package.version); 268 | EditorGUILayout.LabelField("Status",package.status.ToString()); 269 | EditorGUILayout.EndVertical(); 270 | 271 | if (Event.current.type == EventType.MouseUp && Event.current.button == 0 && rect.Contains(Event.current.mousePosition)) 272 | { 273 | var asset = AssetDatabase.LoadAssetAtPath($"{package.assetPath}/package.json"); 274 | Selection.activeObject = asset; 275 | } 276 | 277 | EditorGUILayout.BeginVertical("box"); 278 | maintainPackageManifest.repositoryName = EditorGUILayout.TextField("RepositoryName", maintainPackageManifest.repositoryName); 279 | maintainPackageManifest.author.name = EditorGUILayout.TextField("Author Name",maintainPackageManifest.author.name); 280 | maintainPackageManifest.author.email = EditorGUILayout.TextField("Author E-Mail",maintainPackageManifest.author.email); 281 | maintainPackageManifest.author.url = EditorGUILayout.TextField("Author URL",maintainPackageManifest.author.url); 282 | 283 | maintainPackageManifest.author.twitter = EditorGUILayout.TextField("Twitter",maintainPackageManifest.author.twitter); 284 | maintainPackageManifest.author.github = EditorGUILayout.TextField("GitHub",maintainPackageManifest.author.github); 285 | 286 | var linkStyle = new GUIStyle(EditorStyles.label); 287 | linkStyle.wordWrap = false; 288 | linkStyle.hover.textColor = new Color(0x00 / 255f, 0x78 / 255f, 0xDA / 255f, 1f); 289 | linkStyle.normal.textColor = new Color(0,0,1); 290 | 291 | if (!string.IsNullOrEmpty(maintainPackageManifest.author.twitter)) 292 | { 293 | var twitterUrl = PackageUtility.TwitterUrl(maintainPackageManifest.author.twitter); 294 | if (GUILayout.Button(twitterUrl,linkStyle)) 295 | { 296 | Application.OpenURL(twitterUrl); 297 | } 298 | } 299 | 300 | if (!string.IsNullOrEmpty(maintainPackageManifest.author.github)) 301 | { 302 | var githubUrl = PackageUtility.GithubUrl(maintainPackageManifest.author.github); 303 | if (GUILayout.Button(githubUrl, linkStyle)) 304 | { 305 | Application.OpenURL(githubUrl); 306 | } 307 | var packageUrl = PackageUtility.PackageUrl(maintainPackageManifest.author.github,maintainPackageManifest.repositoryName,maintainPackageManifest.version); 308 | if (GUILayout.Button(packageUrl, linkStyle)) 309 | { 310 | Application.OpenURL(packageUrl); 311 | } 312 | } 313 | 314 | EditorGUILayout.EndVertical(); 315 | 316 | if (GUILayout.Button("Update package.json")) 317 | { 318 | var path = $"{package.assetPath}/package.json"; 319 | var json = File.ReadAllText(path); 320 | var jsonNode = SimpleJSON.JSON.Parse(json); 321 | jsonNode["repositoryName"] = maintainPackageManifest.repositoryName; 322 | jsonNode["author"]["name"] = maintainPackageManifest.author.name; 323 | jsonNode["author"]["email"] = maintainPackageManifest.author.email; 324 | jsonNode["author"]["url"] = maintainPackageManifest.author.url; 325 | jsonNode["author"]["github"] = maintainPackageManifest.author.github; 326 | jsonNode["author"]["twitter"] = maintainPackageManifest.author.twitter; 327 | File.WriteAllText(path,jsonNode.ToString()); 328 | maintainPackageManifest = null; 329 | } 330 | 331 | if (GUILayout.Button("Update Readme")) 332 | { 333 | UpdateReadme(package); 334 | } 335 | 336 | RefreshGUI(); 337 | } 338 | 339 | private async void UpdateReadme(PackageInfo packageInfo) 340 | { 341 | var myPkg = await GetMyPackageInfoAsync(); 342 | var readmeTemplatePath = $"{myPkg.assetPath}/Template/README_TEMPLATE.md"; 343 | 344 | var readmePath = $"{packageInfo.assetPath}/README"; 345 | if (!File.Exists(readmePath)) 346 | { 347 | readmePath = $"{packageInfo.assetPath}/README.md"; 348 | if (!File.Exists(readmePath)) 349 | { 350 | Debug.LogError("Unable to find README or README.md at package asset path"); 351 | return; 352 | } 353 | } 354 | 355 | var manifestPath = $"{packageInfo.assetPath}/package.json"; 356 | if (!File.Exists(manifestPath)) 357 | { 358 | Debug.LogError($"Unable to find package.json at {packageInfo.assetPath}"); 359 | return; 360 | } 361 | 362 | PackageManifest packageManifest = null; 363 | 364 | try 365 | { 366 | packageManifest = JsonUtility.FromJson(File.ReadAllText(manifestPath)); 367 | if (packageManifest == null) 368 | { 369 | Debug.LogError("Failed to read package manifest. FromJson returned null on file text."); 370 | return; 371 | } 372 | } 373 | catch (Exception e) 374 | { 375 | Debug.LogError("Failed to read package manifest format."); 376 | Debug.LogException(e); 377 | return; 378 | } 379 | 380 | if (!ValidatePackageManifest(packageManifest)) 381 | { 382 | Debug.LogError("Update package manifest with required values before updating the readme"); 383 | return; 384 | } 385 | 386 | var oldText = File.ReadAllText(readmePath); 387 | var templateText = File.ReadAllText(readmeTemplatePath); 388 | templateText = PackageUtility.PatchReadmeText(oldText, templateText); 389 | 390 | var readmeText = PackageUtility.CreateReadmeText(templateText, packageManifest); 391 | 392 | File.WriteAllText(readmePath,readmeText); 393 | 394 | EditorUtility.DisplayDialog("Update Readme", "Done", "OK"); 395 | } 396 | 397 | private void RefreshGUI() 398 | { 399 | EditorGUILayout.BeginHorizontal(); 400 | GUILayout.FlexibleSpace(); 401 | if (GUILayout.Button("Refresh")) 402 | { 403 | Refresh(); 404 | } 405 | EditorGUILayout.EndHorizontal(); 406 | } 407 | 408 | private void CreatePackageGUI() 409 | { 410 | if (PackageGuiUtility.SourcePathGui()) 411 | { 412 | Refresh(); 413 | } 414 | 415 | var packageName = serializedObject.FindProperty("packageManifest.name"); 416 | var packageDisplayName = serializedObject.FindProperty("packageManifest.displayName"); 417 | var packageVersion = serializedObject.FindProperty("packageManifest.version"); 418 | var packageDescription = serializedObject.FindProperty("packageManifest.description"); 419 | var packageUnity = serializedObject.FindProperty("packageManifest.unity"); 420 | var packageUnityRelease = serializedObject.FindProperty("packageManifest.unityRelease"); 421 | var packageRepositoryName = serializedObject.FindProperty("packageManifest.repositoryName"); 422 | 423 | var packageAuthorName = serializedObject.FindProperty("packageManifest.author.name"); 424 | var packageAuthorEmail = serializedObject.FindProperty("packageManifest.author.email"); 425 | var packageAuthorUrl = serializedObject.FindProperty("packageManifest.author.url"); 426 | var packageAuthorGithub = serializedObject.FindProperty("packageManifest.author.twitter"); 427 | var packageAuthorTwitter = serializedObject.FindProperty("packageManifest.author.github"); 428 | 429 | GUILayout.BeginVertical("box"); 430 | 431 | EditorGUILayout.LabelField("Package", EditorStyles.boldLabel); 432 | 433 | EditorGUI.BeginChangeCheck(); 434 | EditorGUILayout.PropertyField(packageName); 435 | if (EditorGUI.EndChangeCheck()) 436 | { 437 | //Enforce lowercase constraint 438 | packageName.stringValue = packageName.stringValue.ToLowerInvariant(); 439 | } 440 | 441 | EditorGUILayout.PropertyField(packageRepositoryName); 442 | EditorGUILayout.PropertyField(packageDisplayName); 443 | EditorGUILayout.PropertyField(packageVersion); 444 | EditorGUILayout.PropertyField(packageDescription); 445 | EditorGUILayout.PropertyField(packageUnity); 446 | EditorGUILayout.PropertyField(packageUnityRelease); 447 | 448 | EditorGUILayout.LabelField("Author", EditorStyles.boldLabel); 449 | 450 | EditorGUILayout.PropertyField(packageAuthorName); 451 | EditorGUILayout.PropertyField(packageAuthorEmail); 452 | EditorGUILayout.PropertyField(packageAuthorUrl); 453 | EditorGUILayout.PropertyField(packageAuthorTwitter); 454 | EditorGUILayout.PropertyField(packageAuthorGithub); 455 | 456 | GUILayout.EndVertical(); 457 | 458 | EditorGUILayout.Space(); 459 | 460 | EditorGUILayout.BeginHorizontal(); 461 | if (GUILayout.Button("Create Embeded")) 462 | { 463 | CreateEmbeded(); 464 | } 465 | 466 | if (GUILayout.Button("Create at Source Path")) 467 | { 468 | CreateInSources(); 469 | } 470 | 471 | EditorGUILayout.EndHorizontal(); 472 | 473 | EditorGUILayout.Space(); 474 | } 475 | 476 | private void CreateEmbeded() 477 | { 478 | if (ValidatePackageManifest(packageManifest)) 479 | { 480 | CreateAt($"{Directory.GetCurrentDirectory()}/Packages"); 481 | } 482 | } 483 | 484 | private void CreateInSources() 485 | { 486 | if (ValidatePackageManifest(packageManifest)) 487 | { 488 | CreateAt(PackageSettings.SourcePath); 489 | } 490 | } 491 | 492 | private bool ValidatePackageManifest(PackageManifest manifest) 493 | { 494 | string error = null; 495 | if (string.IsNullOrEmpty(manifest.description)) 496 | { 497 | error = "Package Description Required"; 498 | } 499 | else if (string.IsNullOrEmpty(manifest.version)) 500 | { 501 | error = "Package Version Required"; 502 | } 503 | else if (string.IsNullOrEmpty(manifest.repositoryName)) 504 | { 505 | error ="Package Repository Name Required"; 506 | } 507 | else if (string.IsNullOrEmpty(manifest.displayName)) 508 | { 509 | error = "Package display Name Required"; 510 | } 511 | else if (string.IsNullOrEmpty(manifest.name)) 512 | { 513 | error = "Package Name Required"; 514 | } 515 | else if (string.IsNullOrEmpty(manifest.author.github)) 516 | { 517 | error = "Github username required to build github links"; 518 | } 519 | 520 | if (!string.IsNullOrEmpty(error)) 521 | { 522 | EditorUtility.DisplayDialog("Error", error, "OK"); 523 | EditorApplication.Beep(); 524 | return false; 525 | } 526 | 527 | return true; 528 | } 529 | 530 | private async void CreateAt(string path) 531 | { 532 | var myPkgInfo = await GetMyPackageInfoAsync(); 533 | 534 | var readmeTemplatePath = $"{myPkgInfo.assetPath}/Template/README_TEMPLATE.md"; 535 | var licenseTemplatePath = $"{myPkgInfo.assetPath}/Template/Licenses/MIT LICENSE"; 536 | 537 | if (!Directory.Exists(path)) 538 | { 539 | EditorUtility.DisplayDialog("Error", "Unable to locate packages directory.", "OK"); 540 | return; 541 | } 542 | 543 | var packagePath = $"{path}/{packageManifest.repositoryName}"; 544 | if (Directory.Exists(packagePath)) 545 | { 546 | EditorUtility.DisplayDialog("Error", "A Package with that name already exists.", "OK"); 547 | return; 548 | } 549 | 550 | var json = EditorJsonUtility.ToJson(packageManifest, true); 551 | 552 | Directory.CreateDirectory(packagePath); 553 | Directory.CreateDirectory($"{packagePath}/Editor"); 554 | Directory.CreateDirectory($"{packagePath}/Runtime"); 555 | Directory.CreateDirectory($"{packagePath}/Tests/Editor"); 556 | Directory.CreateDirectory($"{packagePath}/Tests/Runtime"); 557 | 558 | var manifestPath = $"{packagePath}/package.json"; 559 | var readmePath = $"{packagePath}/README.md"; 560 | var licensePath = $"{packagePath}/LICENSE.md"; 561 | 562 | var readmeText = PackageUtility.CreateReadmeText(readmeTemplatePath, packageManifest); 563 | var licenseText = PackageUtility.CreateLicenseText(File.ReadAllText(licenseTemplatePath),packageManifest); 564 | 565 | File.WriteAllText(manifestPath, json); 566 | File.WriteAllText(readmePath, readmeText); 567 | File.WriteAllText(licensePath, licenseText); 568 | 569 | var assemblyName = packageManifest.name; 570 | var editorAssemblyName = $"{packageManifest.name}.Editor"; 571 | var testAssemblyName = $"{assemblyName}.Tests"; 572 | var testEditorAssemblyName = $"{editorAssemblyName}.Tests"; 573 | 574 | var assemblyDefPath = $"{packagePath}/Editor/{editorAssemblyName}.asmdef"; 575 | var editorAssemblyDefPath = $"{packagePath}/Runtime/{assemblyName}.asmdef"; 576 | var testEditorAssemblyDefPath = $"{packagePath}/Tests/Editor/{testEditorAssemblyName}.asmdef"; 577 | var testAssemblyDefPath = $"{packagePath}/Tests/Runtime/{testAssemblyName}.asmdef"; 578 | 579 | File.WriteAllText(assemblyDefPath, 580 | $"{{ \"name\": \"{editorAssemblyName}\", \"references\": [ \"{assemblyName}\" ], \"optionalUnityReferences\": [], \"includePlatforms\": [ \"Editor\" ], \"excludePlatforms\": [] }}"); 581 | File.WriteAllText(editorAssemblyDefPath, $"{{ \"name\": \"{assemblyName}\" }}"); 582 | 583 | File.WriteAllText(testEditorAssemblyDefPath, 584 | $"{{ \"name\": \"{testEditorAssemblyName}\", \"references\": [ \"{assemblyName}\" ], \"optionalUnityReferences\": [\"TestAssemblies\"], \"includePlatforms\": [ \"Editor\" ], \"excludePlatforms\": [] }}"); 585 | File.WriteAllText(testAssemblyDefPath, 586 | $"{{ \"name\": \"{testAssemblyName}\", \"references\": [ \"{assemblyName}\" ], \"optionalUnityReferences\": [\"TestAssemblies\"], \"includePlatforms\": [], \"excludePlatforms\": [] }}"); 587 | 588 | AssetDatabase.Refresh(); 589 | EditorUtility.DisplayDialog("Package Created", "Done!", "Ok"); 590 | Refresh(); 591 | } 592 | 593 | } 594 | } 595 | -------------------------------------------------------------------------------- /Editor/SimpleJson.cs: -------------------------------------------------------------------------------- 1 | /* * * * * 2 | * A simple JSON Parser / builder 3 | * ------------------------------ 4 | * 5 | * It mainly has been written as a simple JSON parser. It can build a JSON string 6 | * from the node-tree, or generate a node tree from any valid JSON string. 7 | * 8 | * Written by Bunny83 9 | * 2012-06-09 10 | * 11 | * Changelog now external. See Changelog.txt 12 | * 13 | * The MIT License (MIT) 14 | * 15 | * Copyright (c) 2012-2019 Markus Göbel (Bunny83) 16 | * 17 | * Permission is hereby granted, free of charge, to any person obtaining a copy 18 | * of this software and associated documentation files (the "Software"), to deal 19 | * in the Software without restriction, including without limitation the rights 20 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | * copies of the Software, and to permit persons to whom the Software is 22 | * furnished to do so, subject to the following conditions: 23 | * 24 | * The above copyright notice and this permission notice shall be included in all 25 | * copies or substantial portions of the Software. 26 | * 27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | * SOFTWARE. 34 | * 35 | * * * * */ 36 | using System; 37 | using System.Collections; 38 | using System.Collections.Generic; 39 | using System.Globalization; 40 | using System.Linq; 41 | using System.Text; 42 | 43 | namespace Gameframe.SimpleJSON 44 | { 45 | public enum JSONNodeType 46 | { 47 | Array = 1, 48 | Object = 2, 49 | String = 3, 50 | Number = 4, 51 | NullValue = 5, 52 | Boolean = 6, 53 | None = 7, 54 | Custom = 0xFF, 55 | } 56 | public enum JSONTextMode 57 | { 58 | Compact, 59 | Indent 60 | } 61 | 62 | public abstract partial class JSONNode 63 | { 64 | #region Enumerators 65 | public struct Enumerator 66 | { 67 | private enum Type { None, Array, Object } 68 | private Type type; 69 | private Dictionary.Enumerator m_Object; 70 | private List.Enumerator m_Array; 71 | public bool IsValid { get { return type != Type.None; } } 72 | public Enumerator(List.Enumerator aArrayEnum) 73 | { 74 | type = Type.Array; 75 | m_Object = default(Dictionary.Enumerator); 76 | m_Array = aArrayEnum; 77 | } 78 | public Enumerator(Dictionary.Enumerator aDictEnum) 79 | { 80 | type = Type.Object; 81 | m_Object = aDictEnum; 82 | m_Array = default(List.Enumerator); 83 | } 84 | public KeyValuePair Current 85 | { 86 | get 87 | { 88 | if (type == Type.Array) 89 | return new KeyValuePair(string.Empty, m_Array.Current); 90 | else if (type == Type.Object) 91 | return m_Object.Current; 92 | return new KeyValuePair(string.Empty, null); 93 | } 94 | } 95 | public bool MoveNext() 96 | { 97 | if (type == Type.Array) 98 | return m_Array.MoveNext(); 99 | else if (type == Type.Object) 100 | return m_Object.MoveNext(); 101 | return false; 102 | } 103 | } 104 | public struct ValueEnumerator 105 | { 106 | private Enumerator m_Enumerator; 107 | public ValueEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } 108 | public ValueEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } 109 | public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } 110 | public JSONNode Current { get { return m_Enumerator.Current.Value; } } 111 | public bool MoveNext() { return m_Enumerator.MoveNext(); } 112 | public ValueEnumerator GetEnumerator() { return this; } 113 | } 114 | public struct KeyEnumerator 115 | { 116 | private Enumerator m_Enumerator; 117 | public KeyEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } 118 | public KeyEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } 119 | public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } 120 | public string Current { get { return m_Enumerator.Current.Key; } } 121 | public bool MoveNext() { return m_Enumerator.MoveNext(); } 122 | public KeyEnumerator GetEnumerator() { return this; } 123 | } 124 | 125 | public class LinqEnumerator : IEnumerator>, IEnumerable> 126 | { 127 | private JSONNode m_Node; 128 | private Enumerator m_Enumerator; 129 | internal LinqEnumerator(JSONNode aNode) 130 | { 131 | m_Node = aNode; 132 | if (m_Node != null) 133 | m_Enumerator = m_Node.GetEnumerator(); 134 | } 135 | public KeyValuePair Current { get { return m_Enumerator.Current; } } 136 | object IEnumerator.Current { get { return m_Enumerator.Current; } } 137 | public bool MoveNext() { return m_Enumerator.MoveNext(); } 138 | 139 | public void Dispose() 140 | { 141 | m_Node = null; 142 | m_Enumerator = new Enumerator(); 143 | } 144 | 145 | public IEnumerator> GetEnumerator() 146 | { 147 | return new LinqEnumerator(m_Node); 148 | } 149 | 150 | public void Reset() 151 | { 152 | if (m_Node != null) 153 | m_Enumerator = m_Node.GetEnumerator(); 154 | } 155 | 156 | IEnumerator IEnumerable.GetEnumerator() 157 | { 158 | return new LinqEnumerator(m_Node); 159 | } 160 | } 161 | 162 | #endregion Enumerators 163 | 164 | #region common interface 165 | 166 | public static bool forceASCII = false; // Use Unicode by default 167 | public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber 168 | public static bool allowLineComments = true; // allow "//"-style comments at the end of a line 169 | 170 | public abstract JSONNodeType Tag { get; } 171 | 172 | public virtual JSONNode this[int aIndex] { get { return null; } set { } } 173 | 174 | public virtual JSONNode this[string aKey] { get { return null; } set { } } 175 | 176 | public virtual string Value { get { return ""; } set { } } 177 | 178 | public virtual int Count { get { return 0; } } 179 | 180 | public virtual bool IsNumber { get { return false; } } 181 | public virtual bool IsString { get { return false; } } 182 | public virtual bool IsBoolean { get { return false; } } 183 | public virtual bool IsNull { get { return false; } } 184 | public virtual bool IsArray { get { return false; } } 185 | public virtual bool IsObject { get { return false; } } 186 | 187 | public virtual bool Inline { get { return false; } set { } } 188 | 189 | public virtual void Add(string aKey, JSONNode aItem) 190 | { 191 | } 192 | public virtual void Add(JSONNode aItem) 193 | { 194 | Add("", aItem); 195 | } 196 | 197 | public virtual JSONNode Remove(string aKey) 198 | { 199 | return null; 200 | } 201 | 202 | public virtual JSONNode Remove(int aIndex) 203 | { 204 | return null; 205 | } 206 | 207 | public virtual JSONNode Remove(JSONNode aNode) 208 | { 209 | return aNode; 210 | } 211 | 212 | public virtual JSONNode Clone() 213 | { 214 | return null; 215 | } 216 | 217 | public virtual IEnumerable Children 218 | { 219 | get 220 | { 221 | yield break; 222 | } 223 | } 224 | 225 | public IEnumerable DeepChildren 226 | { 227 | get 228 | { 229 | foreach (var C in Children) 230 | foreach (var D in C.DeepChildren) 231 | yield return D; 232 | } 233 | } 234 | 235 | public virtual bool HasKey(string aKey) 236 | { 237 | return false; 238 | } 239 | 240 | public virtual JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) 241 | { 242 | return aDefault; 243 | } 244 | 245 | public override string ToString() 246 | { 247 | StringBuilder sb = new StringBuilder(); 248 | WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact); 249 | return sb.ToString(); 250 | } 251 | 252 | public virtual string ToString(int aIndent) 253 | { 254 | StringBuilder sb = new StringBuilder(); 255 | WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent); 256 | return sb.ToString(); 257 | } 258 | internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode); 259 | 260 | public abstract Enumerator GetEnumerator(); 261 | public IEnumerable> Linq { get { return new LinqEnumerator(this); } } 262 | public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } } 263 | public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } } 264 | 265 | #endregion common interface 266 | 267 | #region typecasting properties 268 | 269 | 270 | public virtual double AsDouble 271 | { 272 | get 273 | { 274 | double v = 0.0; 275 | if (double.TryParse(Value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) 276 | return v; 277 | return 0.0; 278 | } 279 | set 280 | { 281 | Value = value.ToString(CultureInfo.InvariantCulture); 282 | } 283 | } 284 | 285 | public virtual int AsInt 286 | { 287 | get { return (int)AsDouble; } 288 | set { AsDouble = value; } 289 | } 290 | 291 | public virtual float AsFloat 292 | { 293 | get { return (float)AsDouble; } 294 | set { AsDouble = value; } 295 | } 296 | 297 | public virtual bool AsBool 298 | { 299 | get 300 | { 301 | bool v = false; 302 | if (bool.TryParse(Value, out v)) 303 | return v; 304 | return !string.IsNullOrEmpty(Value); 305 | } 306 | set 307 | { 308 | Value = (value) ? "true" : "false"; 309 | } 310 | } 311 | 312 | public virtual long AsLong 313 | { 314 | get 315 | { 316 | long val = 0; 317 | if (long.TryParse(Value, out val)) 318 | return val; 319 | return 0L; 320 | } 321 | set 322 | { 323 | Value = value.ToString(); 324 | } 325 | } 326 | 327 | public virtual JSONArray AsArray 328 | { 329 | get 330 | { 331 | return this as JSONArray; 332 | } 333 | } 334 | 335 | public virtual JSONObject AsObject 336 | { 337 | get 338 | { 339 | return this as JSONObject; 340 | } 341 | } 342 | 343 | 344 | #endregion typecasting properties 345 | 346 | #region operators 347 | 348 | public static implicit operator JSONNode(string s) 349 | { 350 | return new JSONString(s); 351 | } 352 | public static implicit operator string(JSONNode d) 353 | { 354 | return (d == null) ? null : d.Value; 355 | } 356 | 357 | public static implicit operator JSONNode(double n) 358 | { 359 | return new JSONNumber(n); 360 | } 361 | public static implicit operator double(JSONNode d) 362 | { 363 | return (d == null) ? 0 : d.AsDouble; 364 | } 365 | 366 | public static implicit operator JSONNode(float n) 367 | { 368 | return new JSONNumber(n); 369 | } 370 | public static implicit operator float(JSONNode d) 371 | { 372 | return (d == null) ? 0 : d.AsFloat; 373 | } 374 | 375 | public static implicit operator JSONNode(int n) 376 | { 377 | return new JSONNumber(n); 378 | } 379 | public static implicit operator int(JSONNode d) 380 | { 381 | return (d == null) ? 0 : d.AsInt; 382 | } 383 | 384 | public static implicit operator JSONNode(long n) 385 | { 386 | if (longAsString) 387 | return new JSONString(n.ToString()); 388 | return new JSONNumber(n); 389 | } 390 | public static implicit operator long(JSONNode d) 391 | { 392 | return (d == null) ? 0L : d.AsLong; 393 | } 394 | 395 | public static implicit operator JSONNode(bool b) 396 | { 397 | return new JSONBool(b); 398 | } 399 | public static implicit operator bool(JSONNode d) 400 | { 401 | return (d == null) ? false : d.AsBool; 402 | } 403 | 404 | public static implicit operator JSONNode(KeyValuePair aKeyValue) 405 | { 406 | return aKeyValue.Value; 407 | } 408 | 409 | public static bool operator ==(JSONNode a, object b) 410 | { 411 | if (ReferenceEquals(a, b)) 412 | return true; 413 | bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator; 414 | bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator; 415 | if (aIsNull && bIsNull) 416 | return true; 417 | return !aIsNull && a.Equals(b); 418 | } 419 | 420 | public static bool operator !=(JSONNode a, object b) 421 | { 422 | return !(a == b); 423 | } 424 | 425 | public override bool Equals(object obj) 426 | { 427 | return ReferenceEquals(this, obj); 428 | } 429 | 430 | public override int GetHashCode() 431 | { 432 | return base.GetHashCode(); 433 | } 434 | 435 | #endregion operators 436 | 437 | [ThreadStatic] 438 | private static StringBuilder m_EscapeBuilder; 439 | internal static StringBuilder EscapeBuilder 440 | { 441 | get 442 | { 443 | if (m_EscapeBuilder == null) 444 | m_EscapeBuilder = new StringBuilder(); 445 | return m_EscapeBuilder; 446 | } 447 | } 448 | internal static string Escape(string aText) 449 | { 450 | var sb = EscapeBuilder; 451 | sb.Length = 0; 452 | if (sb.Capacity < aText.Length + aText.Length / 10) 453 | sb.Capacity = aText.Length + aText.Length / 10; 454 | foreach (char c in aText) 455 | { 456 | switch (c) 457 | { 458 | case '\\': 459 | sb.Append("\\\\"); 460 | break; 461 | case '\"': 462 | sb.Append("\\\""); 463 | break; 464 | case '\n': 465 | sb.Append("\\n"); 466 | break; 467 | case '\r': 468 | sb.Append("\\r"); 469 | break; 470 | case '\t': 471 | sb.Append("\\t"); 472 | break; 473 | case '\b': 474 | sb.Append("\\b"); 475 | break; 476 | case '\f': 477 | sb.Append("\\f"); 478 | break; 479 | default: 480 | if (c < ' ' || (forceASCII && c > 127)) 481 | { 482 | ushort val = c; 483 | sb.Append("\\u").Append(val.ToString("X4")); 484 | } 485 | else 486 | sb.Append(c); 487 | break; 488 | } 489 | } 490 | string result = sb.ToString(); 491 | sb.Length = 0; 492 | return result; 493 | } 494 | 495 | private static JSONNode ParseElement(string token, bool quoted) 496 | { 497 | if (quoted) 498 | return token; 499 | string tmp = token.ToLower(); 500 | if (tmp == "false" || tmp == "true") 501 | return tmp == "true"; 502 | if (tmp == "null") 503 | return JSONNull.CreateOrGet(); 504 | double val; 505 | if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val)) 506 | return val; 507 | else 508 | return token; 509 | } 510 | 511 | public static JSONNode Parse(string aJSON) 512 | { 513 | Stack stack = new Stack(); 514 | JSONNode ctx = null; 515 | int i = 0; 516 | StringBuilder Token = new StringBuilder(); 517 | string TokenName = ""; 518 | bool QuoteMode = false; 519 | bool TokenIsQuoted = false; 520 | while (i < aJSON.Length) 521 | { 522 | switch (aJSON[i]) 523 | { 524 | case '{': 525 | if (QuoteMode) 526 | { 527 | Token.Append(aJSON[i]); 528 | break; 529 | } 530 | stack.Push(new JSONObject()); 531 | if (ctx != null) 532 | { 533 | ctx.Add(TokenName, stack.Peek()); 534 | } 535 | TokenName = ""; 536 | Token.Length = 0; 537 | ctx = stack.Peek(); 538 | break; 539 | 540 | case '[': 541 | if (QuoteMode) 542 | { 543 | Token.Append(aJSON[i]); 544 | break; 545 | } 546 | 547 | stack.Push(new JSONArray()); 548 | if (ctx != null) 549 | { 550 | ctx.Add(TokenName, stack.Peek()); 551 | } 552 | TokenName = ""; 553 | Token.Length = 0; 554 | ctx = stack.Peek(); 555 | break; 556 | 557 | case '}': 558 | case ']': 559 | if (QuoteMode) 560 | { 561 | 562 | Token.Append(aJSON[i]); 563 | break; 564 | } 565 | if (stack.Count == 0) 566 | throw new Exception("JSON Parse: Too many closing brackets"); 567 | 568 | stack.Pop(); 569 | if (Token.Length > 0 || TokenIsQuoted) 570 | ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); 571 | TokenIsQuoted = false; 572 | TokenName = ""; 573 | Token.Length = 0; 574 | if (stack.Count > 0) 575 | ctx = stack.Peek(); 576 | break; 577 | 578 | case ':': 579 | if (QuoteMode) 580 | { 581 | Token.Append(aJSON[i]); 582 | break; 583 | } 584 | TokenName = Token.ToString(); 585 | Token.Length = 0; 586 | TokenIsQuoted = false; 587 | break; 588 | 589 | case '"': 590 | QuoteMode ^= true; 591 | TokenIsQuoted |= QuoteMode; 592 | break; 593 | 594 | case ',': 595 | if (QuoteMode) 596 | { 597 | Token.Append(aJSON[i]); 598 | break; 599 | } 600 | if (Token.Length > 0 || TokenIsQuoted) 601 | ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); 602 | TokenIsQuoted = false; 603 | TokenName = ""; 604 | Token.Length = 0; 605 | TokenIsQuoted = false; 606 | break; 607 | 608 | case '\r': 609 | case '\n': 610 | break; 611 | 612 | case ' ': 613 | case '\t': 614 | if (QuoteMode) 615 | Token.Append(aJSON[i]); 616 | break; 617 | 618 | case '\\': 619 | ++i; 620 | if (QuoteMode) 621 | { 622 | char C = aJSON[i]; 623 | switch (C) 624 | { 625 | case 't': 626 | Token.Append('\t'); 627 | break; 628 | case 'r': 629 | Token.Append('\r'); 630 | break; 631 | case 'n': 632 | Token.Append('\n'); 633 | break; 634 | case 'b': 635 | Token.Append('\b'); 636 | break; 637 | case 'f': 638 | Token.Append('\f'); 639 | break; 640 | case 'u': 641 | { 642 | string s = aJSON.Substring(i + 1, 4); 643 | Token.Append((char)int.Parse( 644 | s, 645 | System.Globalization.NumberStyles.AllowHexSpecifier)); 646 | i += 4; 647 | break; 648 | } 649 | default: 650 | Token.Append(C); 651 | break; 652 | } 653 | } 654 | break; 655 | case '/': 656 | if (allowLineComments && !QuoteMode && i + 1 < aJSON.Length && aJSON[i + 1] == '/') 657 | { 658 | while (++i < aJSON.Length && aJSON[i] != '\n' && aJSON[i] != '\r') ; 659 | break; 660 | } 661 | Token.Append(aJSON[i]); 662 | break; 663 | case '\uFEFF': // remove / ignore BOM (Byte Order Mark) 664 | break; 665 | 666 | default: 667 | Token.Append(aJSON[i]); 668 | break; 669 | } 670 | ++i; 671 | } 672 | if (QuoteMode) 673 | { 674 | throw new Exception("JSON Parse: Quotation marks seems to be messed up."); 675 | } 676 | if (ctx == null) 677 | return ParseElement(Token.ToString(), TokenIsQuoted); 678 | return ctx; 679 | } 680 | 681 | } 682 | // End of JSONNode 683 | 684 | public partial class JSONArray : JSONNode 685 | { 686 | private List m_List = new List(); 687 | private bool inline = false; 688 | public override bool Inline 689 | { 690 | get { return inline; } 691 | set { inline = value; } 692 | } 693 | 694 | public override JSONNodeType Tag { get { return JSONNodeType.Array; } } 695 | public override bool IsArray { get { return true; } } 696 | public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); } 697 | 698 | public override JSONNode this[int aIndex] 699 | { 700 | get 701 | { 702 | if (aIndex < 0 || aIndex >= m_List.Count) 703 | return new JSONLazyCreator(this); 704 | return m_List[aIndex]; 705 | } 706 | set 707 | { 708 | if (value == null) 709 | value = JSONNull.CreateOrGet(); 710 | if (aIndex < 0 || aIndex >= m_List.Count) 711 | m_List.Add(value); 712 | else 713 | m_List[aIndex] = value; 714 | } 715 | } 716 | 717 | public override JSONNode this[string aKey] 718 | { 719 | get { return new JSONLazyCreator(this); } 720 | set 721 | { 722 | if (value == null) 723 | value = JSONNull.CreateOrGet(); 724 | m_List.Add(value); 725 | } 726 | } 727 | 728 | public override int Count 729 | { 730 | get { return m_List.Count; } 731 | } 732 | 733 | public override void Add(string aKey, JSONNode aItem) 734 | { 735 | if (aItem == null) 736 | aItem = JSONNull.CreateOrGet(); 737 | m_List.Add(aItem); 738 | } 739 | 740 | public override JSONNode Remove(int aIndex) 741 | { 742 | if (aIndex < 0 || aIndex >= m_List.Count) 743 | return null; 744 | JSONNode tmp = m_List[aIndex]; 745 | m_List.RemoveAt(aIndex); 746 | return tmp; 747 | } 748 | 749 | public override JSONNode Remove(JSONNode aNode) 750 | { 751 | m_List.Remove(aNode); 752 | return aNode; 753 | } 754 | 755 | public override JSONNode Clone() 756 | { 757 | var node = new JSONArray(); 758 | node.m_List.Capacity = m_List.Capacity; 759 | foreach(var n in m_List) 760 | { 761 | if (n != null) 762 | node.Add(n.Clone()); 763 | else 764 | node.Add(null); 765 | } 766 | return node; 767 | } 768 | 769 | public override IEnumerable Children 770 | { 771 | get 772 | { 773 | foreach (JSONNode N in m_List) 774 | yield return N; 775 | } 776 | } 777 | 778 | 779 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) 780 | { 781 | aSB.Append('['); 782 | int count = m_List.Count; 783 | if (inline) 784 | aMode = JSONTextMode.Compact; 785 | for (int i = 0; i < count; i++) 786 | { 787 | if (i > 0) 788 | aSB.Append(','); 789 | if (aMode == JSONTextMode.Indent) 790 | aSB.AppendLine(); 791 | 792 | if (aMode == JSONTextMode.Indent) 793 | aSB.Append(' ', aIndent + aIndentInc); 794 | m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); 795 | } 796 | if (aMode == JSONTextMode.Indent) 797 | aSB.AppendLine().Append(' ', aIndent); 798 | aSB.Append(']'); 799 | } 800 | } 801 | // End of JSONArray 802 | 803 | public partial class JSONObject : JSONNode 804 | { 805 | private Dictionary m_Dict = new Dictionary(); 806 | 807 | private bool inline = false; 808 | public override bool Inline 809 | { 810 | get { return inline; } 811 | set { inline = value; } 812 | } 813 | 814 | public override JSONNodeType Tag { get { return JSONNodeType.Object; } } 815 | public override bool IsObject { get { return true; } } 816 | 817 | public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); } 818 | 819 | 820 | public override JSONNode this[string aKey] 821 | { 822 | get 823 | { 824 | if (m_Dict.ContainsKey(aKey)) 825 | return m_Dict[aKey]; 826 | else 827 | return new JSONLazyCreator(this, aKey); 828 | } 829 | set 830 | { 831 | if (value == null) 832 | value = JSONNull.CreateOrGet(); 833 | if (m_Dict.ContainsKey(aKey)) 834 | m_Dict[aKey] = value; 835 | else 836 | m_Dict.Add(aKey, value); 837 | } 838 | } 839 | 840 | public override JSONNode this[int aIndex] 841 | { 842 | get 843 | { 844 | if (aIndex < 0 || aIndex >= m_Dict.Count) 845 | return null; 846 | return m_Dict.ElementAt(aIndex).Value; 847 | } 848 | set 849 | { 850 | if (value == null) 851 | value = JSONNull.CreateOrGet(); 852 | if (aIndex < 0 || aIndex >= m_Dict.Count) 853 | return; 854 | string key = m_Dict.ElementAt(aIndex).Key; 855 | m_Dict[key] = value; 856 | } 857 | } 858 | 859 | public override int Count 860 | { 861 | get { return m_Dict.Count; } 862 | } 863 | 864 | public override void Add(string aKey, JSONNode aItem) 865 | { 866 | if (aItem == null) 867 | aItem = JSONNull.CreateOrGet(); 868 | 869 | if (aKey != null) 870 | { 871 | if (m_Dict.ContainsKey(aKey)) 872 | m_Dict[aKey] = aItem; 873 | else 874 | m_Dict.Add(aKey, aItem); 875 | } 876 | else 877 | m_Dict.Add(Guid.NewGuid().ToString(), aItem); 878 | } 879 | 880 | public override JSONNode Remove(string aKey) 881 | { 882 | if (!m_Dict.ContainsKey(aKey)) 883 | return null; 884 | JSONNode tmp = m_Dict[aKey]; 885 | m_Dict.Remove(aKey); 886 | return tmp; 887 | } 888 | 889 | public override JSONNode Remove(int aIndex) 890 | { 891 | if (aIndex < 0 || aIndex >= m_Dict.Count) 892 | return null; 893 | var item = m_Dict.ElementAt(aIndex); 894 | m_Dict.Remove(item.Key); 895 | return item.Value; 896 | } 897 | 898 | public override JSONNode Remove(JSONNode aNode) 899 | { 900 | try 901 | { 902 | var item = m_Dict.Where(k => k.Value == aNode).First(); 903 | m_Dict.Remove(item.Key); 904 | return aNode; 905 | } 906 | catch 907 | { 908 | return null; 909 | } 910 | } 911 | 912 | public override JSONNode Clone() 913 | { 914 | var node = new JSONObject(); 915 | foreach (var n in m_Dict) 916 | { 917 | node.Add(n.Key, n.Value.Clone()); 918 | } 919 | return node; 920 | } 921 | 922 | public override bool HasKey(string aKey) 923 | { 924 | return m_Dict.ContainsKey(aKey); 925 | } 926 | 927 | public override JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) 928 | { 929 | JSONNode res; 930 | if (m_Dict.TryGetValue(aKey, out res)) 931 | return res; 932 | return aDefault; 933 | } 934 | 935 | public override IEnumerable Children 936 | { 937 | get 938 | { 939 | foreach (KeyValuePair N in m_Dict) 940 | yield return N.Value; 941 | } 942 | } 943 | 944 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) 945 | { 946 | aSB.Append('{'); 947 | bool first = true; 948 | if (inline) 949 | aMode = JSONTextMode.Compact; 950 | foreach (var k in m_Dict) 951 | { 952 | if (!first) 953 | aSB.Append(','); 954 | first = false; 955 | if (aMode == JSONTextMode.Indent) 956 | aSB.AppendLine(); 957 | if (aMode == JSONTextMode.Indent) 958 | aSB.Append(' ', aIndent + aIndentInc); 959 | aSB.Append('\"').Append(Escape(k.Key)).Append('\"'); 960 | if (aMode == JSONTextMode.Compact) 961 | aSB.Append(':'); 962 | else 963 | aSB.Append(" : "); 964 | k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); 965 | } 966 | if (aMode == JSONTextMode.Indent) 967 | aSB.AppendLine().Append(' ', aIndent); 968 | aSB.Append('}'); 969 | } 970 | 971 | } 972 | // End of JSONObject 973 | 974 | public partial class JSONString : JSONNode 975 | { 976 | private string m_Data; 977 | 978 | public override JSONNodeType Tag { get { return JSONNodeType.String; } } 979 | public override bool IsString { get { return true; } } 980 | 981 | public override Enumerator GetEnumerator() { return new Enumerator(); } 982 | 983 | 984 | public override string Value 985 | { 986 | get { return m_Data; } 987 | set 988 | { 989 | m_Data = value; 990 | } 991 | } 992 | 993 | public JSONString(string aData) 994 | { 995 | m_Data = aData; 996 | } 997 | public override JSONNode Clone() 998 | { 999 | return new JSONString(m_Data); 1000 | } 1001 | 1002 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) 1003 | { 1004 | aSB.Append('\"').Append(Escape(m_Data)).Append('\"'); 1005 | } 1006 | public override bool Equals(object obj) 1007 | { 1008 | if (base.Equals(obj)) 1009 | return true; 1010 | string s = obj as string; 1011 | if (s != null) 1012 | return m_Data == s; 1013 | JSONString s2 = obj as JSONString; 1014 | if (s2 != null) 1015 | return m_Data == s2.m_Data; 1016 | return false; 1017 | } 1018 | public override int GetHashCode() 1019 | { 1020 | return m_Data.GetHashCode(); 1021 | } 1022 | } 1023 | // End of JSONString 1024 | 1025 | public partial class JSONNumber : JSONNode 1026 | { 1027 | private double m_Data; 1028 | 1029 | public override JSONNodeType Tag { get { return JSONNodeType.Number; } } 1030 | public override bool IsNumber { get { return true; } } 1031 | public override Enumerator GetEnumerator() { return new Enumerator(); } 1032 | 1033 | public override string Value 1034 | { 1035 | get { return m_Data.ToString(CultureInfo.InvariantCulture); } 1036 | set 1037 | { 1038 | double v; 1039 | if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) 1040 | m_Data = v; 1041 | } 1042 | } 1043 | 1044 | public override double AsDouble 1045 | { 1046 | get { return m_Data; } 1047 | set { m_Data = value; } 1048 | } 1049 | public override long AsLong 1050 | { 1051 | get { return (long)m_Data; } 1052 | set { m_Data = value; } 1053 | } 1054 | 1055 | public JSONNumber(double aData) 1056 | { 1057 | m_Data = aData; 1058 | } 1059 | 1060 | public JSONNumber(string aData) 1061 | { 1062 | Value = aData; 1063 | } 1064 | 1065 | public override JSONNode Clone() 1066 | { 1067 | return new JSONNumber(m_Data); 1068 | } 1069 | 1070 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) 1071 | { 1072 | aSB.Append(Value); 1073 | } 1074 | private static bool IsNumeric(object value) 1075 | { 1076 | return value is int || value is uint 1077 | || value is float || value is double 1078 | || value is decimal 1079 | || value is long || value is ulong 1080 | || value is short || value is ushort 1081 | || value is sbyte || value is byte; 1082 | } 1083 | public override bool Equals(object obj) 1084 | { 1085 | if (obj == null) 1086 | return false; 1087 | if (base.Equals(obj)) 1088 | return true; 1089 | JSONNumber s2 = obj as JSONNumber; 1090 | if (s2 != null) 1091 | return m_Data == s2.m_Data; 1092 | if (IsNumeric(obj)) 1093 | return Convert.ToDouble(obj) == m_Data; 1094 | return false; 1095 | } 1096 | public override int GetHashCode() 1097 | { 1098 | return m_Data.GetHashCode(); 1099 | } 1100 | } 1101 | // End of JSONNumber 1102 | 1103 | public partial class JSONBool : JSONNode 1104 | { 1105 | private bool m_Data; 1106 | 1107 | public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } } 1108 | public override bool IsBoolean { get { return true; } } 1109 | public override Enumerator GetEnumerator() { return new Enumerator(); } 1110 | 1111 | public override string Value 1112 | { 1113 | get { return m_Data.ToString(); } 1114 | set 1115 | { 1116 | bool v; 1117 | if (bool.TryParse(value, out v)) 1118 | m_Data = v; 1119 | } 1120 | } 1121 | public override bool AsBool 1122 | { 1123 | get { return m_Data; } 1124 | set { m_Data = value; } 1125 | } 1126 | 1127 | public JSONBool(bool aData) 1128 | { 1129 | m_Data = aData; 1130 | } 1131 | 1132 | public JSONBool(string aData) 1133 | { 1134 | Value = aData; 1135 | } 1136 | 1137 | public override JSONNode Clone() 1138 | { 1139 | return new JSONBool(m_Data); 1140 | } 1141 | 1142 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) 1143 | { 1144 | aSB.Append((m_Data) ? "true" : "false"); 1145 | } 1146 | public override bool Equals(object obj) 1147 | { 1148 | if (obj == null) 1149 | return false; 1150 | if (obj is bool) 1151 | return m_Data == (bool)obj; 1152 | return false; 1153 | } 1154 | public override int GetHashCode() 1155 | { 1156 | return m_Data.GetHashCode(); 1157 | } 1158 | } 1159 | // End of JSONBool 1160 | 1161 | public partial class JSONNull : JSONNode 1162 | { 1163 | static JSONNull m_StaticInstance = new JSONNull(); 1164 | public static bool reuseSameInstance = true; 1165 | public static JSONNull CreateOrGet() 1166 | { 1167 | if (reuseSameInstance) 1168 | return m_StaticInstance; 1169 | return new JSONNull(); 1170 | } 1171 | private JSONNull() { } 1172 | 1173 | public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } } 1174 | public override bool IsNull { get { return true; } } 1175 | public override Enumerator GetEnumerator() { return new Enumerator(); } 1176 | 1177 | public override string Value 1178 | { 1179 | get { return "null"; } 1180 | set { } 1181 | } 1182 | public override bool AsBool 1183 | { 1184 | get { return false; } 1185 | set { } 1186 | } 1187 | 1188 | public override JSONNode Clone() 1189 | { 1190 | return CreateOrGet(); 1191 | } 1192 | 1193 | public override bool Equals(object obj) 1194 | { 1195 | if (object.ReferenceEquals(this, obj)) 1196 | return true; 1197 | return (obj is JSONNull); 1198 | } 1199 | public override int GetHashCode() 1200 | { 1201 | return 0; 1202 | } 1203 | 1204 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) 1205 | { 1206 | aSB.Append("null"); 1207 | } 1208 | } 1209 | // End of JSONNull 1210 | 1211 | internal partial class JSONLazyCreator : JSONNode 1212 | { 1213 | private JSONNode m_Node = null; 1214 | private string m_Key = null; 1215 | public override JSONNodeType Tag { get { return JSONNodeType.None; } } 1216 | public override Enumerator GetEnumerator() { return new Enumerator(); } 1217 | 1218 | public JSONLazyCreator(JSONNode aNode) 1219 | { 1220 | m_Node = aNode; 1221 | m_Key = null; 1222 | } 1223 | 1224 | public JSONLazyCreator(JSONNode aNode, string aKey) 1225 | { 1226 | m_Node = aNode; 1227 | m_Key = aKey; 1228 | } 1229 | 1230 | private T Set(T aVal) where T : JSONNode 1231 | { 1232 | if (m_Key == null) 1233 | m_Node.Add(aVal); 1234 | else 1235 | m_Node.Add(m_Key, aVal); 1236 | m_Node = null; // Be GC friendly. 1237 | return aVal; 1238 | } 1239 | 1240 | public override JSONNode this[int aIndex] 1241 | { 1242 | get { return new JSONLazyCreator(this); } 1243 | set { Set(new JSONArray()).Add(value); } 1244 | } 1245 | 1246 | public override JSONNode this[string aKey] 1247 | { 1248 | get { return new JSONLazyCreator(this, aKey); } 1249 | set { Set(new JSONObject()).Add(aKey, value); } 1250 | } 1251 | 1252 | public override void Add(JSONNode aItem) 1253 | { 1254 | Set(new JSONArray()).Add(aItem); 1255 | } 1256 | 1257 | public override void Add(string aKey, JSONNode aItem) 1258 | { 1259 | Set(new JSONObject()).Add(aKey, aItem); 1260 | } 1261 | 1262 | public static bool operator ==(JSONLazyCreator a, object b) 1263 | { 1264 | if (b == null) 1265 | return true; 1266 | return System.Object.ReferenceEquals(a, b); 1267 | } 1268 | 1269 | public static bool operator !=(JSONLazyCreator a, object b) 1270 | { 1271 | return !(a == b); 1272 | } 1273 | 1274 | public override bool Equals(object obj) 1275 | { 1276 | if (obj == null) 1277 | return true; 1278 | return System.Object.ReferenceEquals(this, obj); 1279 | } 1280 | 1281 | public override int GetHashCode() 1282 | { 1283 | return 0; 1284 | } 1285 | 1286 | public override int AsInt 1287 | { 1288 | get { Set(new JSONNumber(0)); return 0; } 1289 | set { Set(new JSONNumber(value)); } 1290 | } 1291 | 1292 | public override float AsFloat 1293 | { 1294 | get { Set(new JSONNumber(0.0f)); return 0.0f; } 1295 | set { Set(new JSONNumber(value)); } 1296 | } 1297 | 1298 | public override double AsDouble 1299 | { 1300 | get { Set(new JSONNumber(0.0)); return 0.0; } 1301 | set { Set(new JSONNumber(value)); } 1302 | } 1303 | 1304 | public override long AsLong 1305 | { 1306 | get 1307 | { 1308 | if (longAsString) 1309 | Set(new JSONString("0")); 1310 | else 1311 | Set(new JSONNumber(0.0)); 1312 | return 0L; 1313 | } 1314 | set 1315 | { 1316 | if (longAsString) 1317 | Set(new JSONString(value.ToString())); 1318 | else 1319 | Set(new JSONNumber(value)); 1320 | } 1321 | } 1322 | 1323 | public override bool AsBool 1324 | { 1325 | get { Set(new JSONBool(false)); return false; } 1326 | set { Set(new JSONBool(value)); } 1327 | } 1328 | 1329 | public override JSONArray AsArray 1330 | { 1331 | get { return Set(new JSONArray()); } 1332 | } 1333 | 1334 | public override JSONObject AsObject 1335 | { 1336 | get { return Set(new JSONObject()); } 1337 | } 1338 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) 1339 | { 1340 | aSB.Append("null"); 1341 | } 1342 | } 1343 | // End of JSONLazyCreator 1344 | 1345 | public static class JSON 1346 | { 1347 | public static JSONNode Parse(string aJSON) 1348 | { 1349 | return JSONNode.Parse(aJSON); 1350 | } 1351 | } 1352 | } --------------------------------------------------------------------------------