├── gradle.properties ├── settings.gradle.kts ├── lib └── junit-4.12.jar ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ └── gradle-plugins │ │ │ └── com.tangnb.plugin.properties │ └── kotlin │ │ ├── internal │ │ ├── filetype │ │ │ ├── AnimFileRemover.kt │ │ │ ├── MenuFileRemover.kt │ │ │ ├── ColorFileRemover.kt │ │ │ ├── AnimatorFileRemover.kt │ │ │ ├── LayoutFileRemover.kt │ │ │ ├── MipmapFileRemover.kt │ │ │ ├── DrawableFileRemover.kt │ │ │ └── FileRemover.kt │ │ ├── valuetype │ │ │ ├── IdXmlValueRemover.kt │ │ │ ├── BoolXmlValueRemover.kt │ │ │ ├── ColorXmlValueRemover.kt │ │ │ ├── DimenXmlValueRemover.kt │ │ │ ├── StringXmlValueRemover.kt │ │ │ ├── IntegerXmlValueRemover.kt │ │ │ ├── AttrXmlValueRemover.kt │ │ │ ├── StyleXmlValueRemover.kt │ │ │ ├── ThemeXmlValueRemover.kt │ │ │ └── XmlValueRemover.kt │ │ └── AbstractRemover.kt │ │ ├── RemoveResExt.kt │ │ ├── util │ │ ├── LogUtil.kt │ │ ├── ThreadPoolManager.kt │ │ └── SearchPattern.kt │ │ └── RemoveResPlugin.kt └── test │ └── kotlin │ ├── internal │ ├── AbstractRemoverTest.kt │ ├── fileType │ │ ├── FileRemoverTest.kt │ │ ├── MipmapFileRemoverTest.kt │ │ ├── AnimFileRemoverTest.kt │ │ ├── MenuFileRemoverTest.kt │ │ ├── ColorFileRemoverTest.kt │ │ ├── DrawableFileRemoverTest.kt │ │ ├── AnimatorFileRemoverTest.kt │ │ └── LayoutFileRemoverTest.kt │ └── valuetype │ │ ├── AttrXmlValueRemoverTest.kt │ │ ├── BoolXmlValueRemoverTest.kt │ │ ├── IntegerXmlValueRemoverTest.kt │ │ ├── IdXmlValueRemoverTest.kt │ │ ├── ThemeXmlValueRemoverTest.kt │ │ ├── DimenXmlValueRemoverTest.kt │ │ ├── ColorXmlValueRemoverTest.kt │ │ ├── StringXmlValueRemoverTest.kt │ │ └── StyleXmlValueRemoverTest.kt │ └── util │ ├── SearchPatternTest.kt │ └── DirectoryMatcherTest.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── .github └── workflows │ └── blank.yml ├── gradlew.bat └── gradlew /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "removeRes" 2 | -------------------------------------------------------------------------------- /lib/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangHuaiZhe/removeRes/HEAD/lib/junit-4.12.jar -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/com.tangnb.plugin.properties: -------------------------------------------------------------------------------- 1 | implementation-class=RemoveResPlugin 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TangHuaiZhe/removeRes/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | /.idea 10 | out/ 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/AnimFileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | class AnimFileRemover constructor( 4 | override val fileType: String = "anim", 5 | override val resType: String = "anim" 6 | ) : FileRemover( 7 | fileType, 8 | resType 9 | ) -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/MenuFileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | class MenuFileRemover constructor( 4 | override val fileType: String = "menu", 5 | override val resType: String = "menu" 6 | ) : 7 | FileRemover(fileType, resType) { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/ColorFileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | class ColorFileRemover constructor( 4 | override val fileType: String = "color", 5 | override val resType: String = "color" 6 | ) : FileRemover( 7 | fileType, 8 | resType 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/AnimatorFileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | class AnimatorFileRemover constructor( 4 | override val fileType: String = "animator", 5 | override val resType: String = "animator" 6 | ) : FileRemover( 7 | fileType, 8 | resType 9 | ) 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/IdXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-12 11:20 6 | * description: 7 | */ 8 | class IdXmlValueRemover( 9 | fileType: String = "id", 10 | resourceName: String = "id", 11 | tagName: String = "item" 12 | ) : XmlValueRemover(fileType, resourceName, tagName) -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/BoolXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-12 11:15 6 | * description: 7 | */ 8 | class BoolXmlValueRemover( 9 | fileType: String = "bool", 10 | resourceName: String = "bool", 11 | tagName: String = "bool" 12 | ) : XmlValueRemover(fileType, resourceName, tagName) -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/ColorXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-12 11:17 6 | * description: 7 | */ 8 | class ColorXmlValueRemover( 9 | fileType: String = "color", 10 | resourceName: String = "color", 11 | tagName: String = "color" 12 | ) : XmlValueRemover(fileType, resourceName, tagName) -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/DimenXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-12 11:17 6 | * description: 7 | */ 8 | class DimenXmlValueRemover( 9 | fileType: String = "dimen", 10 | resourceName: String = "dimen", 11 | tagName: String = "dimen" 12 | ) : XmlValueRemover(fileType, resourceName, tagName) -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/StringXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-12 11:21 6 | * description: 7 | */ 8 | class StringXmlValueRemover( 9 | fileType: String = "string", 10 | resourceName: String = "string", 11 | tagName: String = "string" 12 | ) : XmlValueRemover(fileType, resourceName, tagName) -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/IntegerXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-12 11:20 6 | * description: 7 | */ 8 | class IntegerXmlValueRemover( 9 | fileType: String = "integer", 10 | resourceName: String = "integer", 11 | tagName: String = "integer" 12 | ) : XmlValueRemover(fileType, resourceName, tagName) -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/LayoutFileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | import util.SearchPattern 4 | 5 | class LayoutFileRemover constructor( 6 | override val fileType: String = "layout", 7 | override val resType: String = "layout", 8 | override val mainType: SearchPattern.Type = SearchPattern.Type.LAYOUT 9 | ) : FileRemover( 10 | fileType, 11 | resType, 12 | mainType 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/MipmapFileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | import util.SearchPattern 4 | 5 | class MipmapFileRemover constructor( 6 | override val fileType: String = "mipmap", 7 | override val resType: String = "mipmap", 8 | override val mainType: SearchPattern.Type = SearchPattern.Type.DRAWABLE 9 | ) : FileRemover( 10 | fileType, 11 | resType, 12 | mainType 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/DrawableFileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | import util.SearchPattern 4 | 5 | class DrawableFileRemover constructor( 6 | override val fileType: String = "drawable", 7 | override val resType: String = "drawable", 8 | override val mainType: SearchPattern.Type = SearchPattern.Type.DRAWABLE 9 | ) : FileRemover( 10 | fileType, 11 | resType, 12 | mainType 13 | ) 14 | 15 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/AttrXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-12 10:27 6 | * description: 7 | */ 8 | class AttrXmlValueRemover constructor( 9 | fileType: String = "attrs", 10 | resourceName: String = "styleable", 11 | override var tagName: String = "declare-styleable" 12 | ) : XmlValueRemover( 13 | fileType, 14 | resourceName, 15 | tagName 16 | ) -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/StyleXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import util.SearchPattern 4 | 5 | /** 6 | * author: tang 7 | * created on: 2019-08-12 11:22 8 | * description: 9 | */ 10 | class StyleXmlValueRemover( 11 | fileType: String = "style", 12 | resourceName: String = "style", 13 | tagName: String = "style", 14 | type: SearchPattern.Type = SearchPattern.Type.STYLE 15 | ) : XmlValueRemover(fileType, resourceName, tagName, type) -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/ThemeXmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import util.SearchPattern 4 | 5 | /** 6 | * author: tang 7 | * created on: 2019-08-12 11:23 8 | * description: 9 | */ 10 | class ThemeXmlValueRemover( 11 | fileType: String = "theme", 12 | resourceName: String = "style", 13 | tagName: String = "style", 14 | type: SearchPattern.Type = SearchPattern.Type.STYLE 15 | ) : XmlValueRemover(fileType, resourceName, tagName, type) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # removeRes 2 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) 3 | 4 | 5 | 移除Android项目中没有使用到的资源文件和xml value. 6 | 7 | 集成: 8 | ```gradle 9 | buildscript { 10 | repositories { 11 | maven { 12 | url "https://plugins.gradle.org/m2/" 13 | } 14 | } 15 | dependencies { 16 | classpath "gradle.plugin.com.tangnb.plugin:removeRes:1.0.7" 17 | } 18 | } 19 | 20 | apply plugin: "com.tangnb.RemoveRes" 21 | ``` 22 | 23 | 然后运行: 24 | `./gradlew RemoveRes --parallel` 25 | -------------------------------------------------------------------------------- /src/main/kotlin/RemoveResExt.kt: -------------------------------------------------------------------------------- 1 | import internal.AbstractRemover 2 | import java.util.ArrayList 3 | 4 | /** 5 | * author: tang 6 | * created on: 2019-08-07 16:13 7 | * description: 8 | */ 9 | open class RemoveResExt { 10 | var extraRemovers: List = ArrayList() 11 | var excludeNames: ArrayList = ArrayList() 12 | var dryRun = false 13 | var openRemoveFile = true 14 | var openRemoveXmlValues = false 15 | var logOpen = true 16 | 17 | companion object { 18 | const val name = "RemoveRes" 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/AbstractRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import internal.valuetype.AttrXmlValueRemover 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | /** 8 | * author: tang created on: 2019-08-16 11:21 description: 9 | */ 10 | class AbstractRemoverTest{ 11 | val remover = AttrXmlValueRemover() 12 | 13 | @Test 14 | fun testIsPatternMatched(){ 15 | Assert.assertTrue( 16 | remover.isPatternMatched("/Users/tang/Code/lianxin/zhangxin/social_main/imageeditengine/src/main/res/values/attrs.xml", 17 | "attr.*")) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/util/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | object LogUtil { 4 | 5 | private const val ANSI_RESET = "\u001B[0m" 6 | private const val ANSI_GREEN = "\u001B[32m" 7 | private const val ANSI_YELLOW = "\u001B[33m" 8 | private const val ANSI_BLUE = "\u001B[34m" 9 | 10 | fun green(text: String) { 11 | if (RemoveResPlugin.logOpen) { 12 | println(ANSI_GREEN + text + ANSI_RESET) 13 | } 14 | } 15 | 16 | fun yellow(text: String) { 17 | if (RemoveResPlugin.logOpen) { 18 | println(ANSI_YELLOW + text + ANSI_RESET) 19 | } 20 | } 21 | 22 | fun blue(text: String) { 23 | if (RemoveResPlugin.logOpen) { 24 | println(ANSI_BLUE + text + ANSI_RESET) 25 | } 26 | } 27 | 28 | fun info(text: String) { 29 | if (RemoveResPlugin.logOpen) { 30 | println(text) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/kotlin/util/SearchPatternTest.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | /** 7 | * author: tang created on: 2019-08-12 16:32 description: 8 | */ 9 | class SearchPatternTest { 10 | 11 | @Test 12 | fun toCamelCaseWithUnderscore() { 13 | var tagName = "TextTitle" 14 | Assert.assertEquals("TextTitle", SearchPattern.toCamelCaseWithUnderscore(tagName)) 15 | 16 | tagName = "TextTitle.long" 17 | Assert.assertEquals("TextTitle_Long", SearchPattern.toCamelCaseWithUnderscore(tagName)) 18 | 19 | tagName = "TextTitle.Long" 20 | Assert.assertEquals("TextTitle_Long", SearchPattern.toCamelCaseWithUnderscore(tagName)) 21 | 22 | tagName = "textTitle.long" 23 | Assert.assertEquals("textTitle_Long", SearchPattern.toCamelCaseWithUnderscore(tagName)) 24 | 25 | tagName = "text_title.long" 26 | Assert.assertEquals("text_title_Long", SearchPattern.toCamelCaseWithUnderscore(tagName)) 27 | 28 | tagName = "TextTitle.Long.Gray" 29 | Assert.assertEquals("TextTitle_Long_Gray", SearchPattern.toCamelCaseWithUnderscore(tagName)) 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/FileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.DrawableFileRemover 4 | import internal.filetype.FileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | 8 | /** 9 | * author: tang 10 | * created on: 2019-08-09 15:34 11 | * description: 12 | */ 13 | class FileRemoverTest { 14 | private val remover: FileRemover = DrawableFileRemover() 15 | 16 | @Test 17 | fun checkExcludeNames() { 18 | 19 | var excludeNames: List = listOf("ic_launcher.xml") 20 | remover.excludeNames.clear() 21 | remover.excludeNames.addAll(excludeNames) 22 | Assert.assertTrue(remover.isMatchedExcludeNames("/res/mipmap/ic_launcher.xml")) 23 | 24 | excludeNames = listOf("ic_launcher") 25 | remover.excludeNames.clear() 26 | remover.excludeNames.addAll(excludeNames) 27 | Assert.assertTrue(remover.isMatchedExcludeNames("/res/mipmap/ic_launcher.xml")) 28 | 29 | 30 | excludeNames = listOf("ic_settings.xml", "ic_launcher.xml") 31 | remover.excludeNames.clear() 32 | remover.excludeNames.addAll(excludeNames) 33 | Assert.assertTrue(remover.isMatchedExcludeNames("/res/mipmap/ic_launcher.xml")) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /.github/workflows/blank.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v2 27 | 28 | # Runs a single command using the runners shell 29 | - name: Build 30 | run: ./gradlew clean assemble 31 | 32 | # Runs a set of commands using the runners shell 33 | - name: Run a multi-line script 34 | run: | 35 | echo Add other actions to build, 36 | echo test, and deploy your project. 37 | -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/AttrXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang created on: 2019-08-12 13:20 description: 9 | */ 10 | class AttrXmlValueRemoverTest { 11 | 12 | private val remover: XmlValueRemover = AttrXmlValueRemover() 13 | 14 | @Test 15 | fun testPatternMatches() { 16 | val pattern = remover.createSearchPattern("CustomView") 17 | var fileText = "R.styleable.CustomView;" 18 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 19 | //todo 避免Android资源文件的干扰 20 | // fileText = "android.R.styleable.CustomView;" 21 | // Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 22 | fileText = "R.style.CustomView" 23 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 24 | } 25 | 26 | @Test 27 | fun getFileType() { 28 | Assert.assertEquals("attrs", remover.fileType) 29 | } 30 | 31 | @Test 32 | fun getResourceName() { 33 | Assert.assertEquals("styleable", remover.resType) 34 | } 35 | 36 | @Test 37 | fun getTagName() { 38 | Assert.assertEquals("declare-styleable", remover.tagName) 39 | } 40 | 41 | @Test 42 | fun getResourceType() { 43 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/util/ThreadPoolManager.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import org.gradle.internal.impldep.org.eclipse.jgit.annotations.NonNull 4 | import java.util.concurrent.* 5 | import java.util.concurrent.atomic.AtomicInteger 6 | 7 | /** 8 | * author: tang 9 | * created on: 2018/3/20 16:05 10 | * description:线程池管理类 11 | */ 12 | class ThreadPoolManager private constructor() { 13 | private val cpuCount = Runtime.getRuntime().availableProcessors() 14 | private val executor: ThreadPoolExecutor = Executors.newFixedThreadPool(2 * cpuCount + 1) as ThreadPoolExecutor 15 | 16 | val isOver: Boolean 17 | get() = executor.queue.isEmpty() && executor.activeCount == 0 18 | 19 | /** 20 | * 执行任务 21 | */ 22 | fun execute(runnable: Runnable) { 23 | executor.execute(runnable) 24 | } 25 | 26 | override fun toString(): String { 27 | return "ThreadPoolManager(executor=$executor)" 28 | } 29 | 30 | companion object { 31 | 32 | @Volatile 33 | private var mInstance: ThreadPoolManager? = null 34 | 35 | val instance: ThreadPoolManager 36 | get() { 37 | if (mInstance == null) { 38 | synchronized(ThreadPoolManager::class.java) { 39 | if (mInstance == null) { 40 | mInstance = ThreadPoolManager() 41 | } 42 | } 43 | } 44 | return mInstance!! 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/BoolXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang 9 | * created on: 2019-08-12 14:05 10 | * description: 11 | */ 12 | class BoolXmlValueRemoverTest { 13 | 14 | private val remover: XmlValueRemover = BoolXmlValueRemover() 15 | 16 | @Test 17 | fun testPatternMatches() { 18 | val pattern = remover.createSearchPattern("pref_value") 19 | var fileText = "R.bool.pref_value;" 20 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 21 | fileText = "R.bool.pref" 22 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 23 | 24 | fileText = "@bool/pref_value\"" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "@bool/pref_value<" 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "@bool/pref_value2\"" 31 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 32 | 33 | fileText = "@bool/pref_value" 34 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 35 | } 36 | 37 | @Test 38 | fun getFileType() { 39 | Assert.assertEquals("bool", remover.fileType) 40 | } 41 | 42 | @Test 43 | fun getResourceName() { 44 | Assert.assertEquals("bool", remover.resType) 45 | } 46 | 47 | @Test 48 | fun getTagName() { 49 | Assert.assertEquals("bool", remover.tagName) 50 | } 51 | 52 | @Test 53 | fun getResourceType() { 54 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/IntegerXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang created on: 2019-08-12 13:20 description: 9 | */ 10 | class IntegerXmlValueRemoverTest { 11 | 12 | private val remover = IntegerXmlValueRemover() 13 | 14 | @Test 15 | fun testPatternMatches() { 16 | val pattern = remover.createSearchPattern("max_length") 17 | var fileText = "R.integer.max_length;" 18 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 19 | 20 | fileText = "R.integer.max;" 21 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 22 | 23 | fileText = "@integer/max_length\"" 24 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 25 | 26 | fileText = "@integer/max_length<" 27 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 28 | 29 | fileText = "@integer/max_length2\"" 30 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 31 | 32 | fileText = "@integer/max_length" 33 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 34 | } 35 | 36 | @Test 37 | fun getFileType() { 38 | Assert.assertEquals("integer", remover.fileType) 39 | } 40 | 41 | @Test 42 | fun getResourceName() { 43 | Assert.assertEquals("integer", remover.resType) 44 | } 45 | 46 | @Test 47 | fun getTagName() { 48 | Assert.assertEquals("integer", remover.tagName) 49 | } 50 | 51 | @Test 52 | fun getResourceType() { 53 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/MipmapFileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.FileRemover 4 | import internal.filetype.MipmapFileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import util.SearchPattern 8 | 9 | /** 10 | * author: tang 11 | * created on: 2019-08-09 16:39 12 | * description: 13 | */ 14 | class MipmapFileRemoverTest { 15 | 16 | private val remover: FileRemover = MipmapFileRemover() 17 | 18 | @Test 19 | fun testPatternMatches() { 20 | val pattern = remover.createSearchPattern("ic_launcher") 21 | var fileText = "R.mipmap.ic_launcher)" 22 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 23 | 24 | fileText = "R.mipmap.ic_launcher\"" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "@mipmap/ic_launcher\"" 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "@mipmap/ic_launcher<" 31 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 32 | 33 | fileText = "R.drawable.ic_launch" 34 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 35 | 36 | fileText = "@mipmap/ic_launcher_round\"" 37 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 38 | 39 | } 40 | 41 | @Test 42 | fun getFileType() { 43 | Assert.assertEquals("mipmap", remover.fileType) 44 | } 45 | 46 | @Test 47 | fun getResourceName() { 48 | Assert.assertEquals("mipmap", remover.resType) 49 | } 50 | 51 | @Test 52 | fun getType() { 53 | Assert.assertEquals(SearchPattern.Type.DRAWABLE, remover.mainType) 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/IdXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang created on: 2019-08-12 13:20 description: 9 | */ 10 | class IdXmlValueRemoverTest { 11 | 12 | private val remover = IdXmlValueRemover() 13 | 14 | @Test 15 | fun testPatternMatches() { 16 | val pattern = remover.createSearchPattern("view_id") 17 | var fileText = "R.id.view_id;" 18 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 19 | 20 | fileText = "R.id.view_id)" 21 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 22 | 23 | fileText = "R.id.view;" 24 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 25 | 26 | fileText = "@id/view_id\"" 27 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 28 | 29 | fileText = "@id/view_id\"" 30 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 31 | fileText = "@id/view_id<" 32 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 33 | fileText = "@id/view_id2\"" 34 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 35 | } 36 | 37 | @Test 38 | fun getFileType() { 39 | Assert.assertEquals("id", remover.fileType) 40 | } 41 | 42 | @Test 43 | fun getResourceName() { 44 | Assert.assertEquals("id", remover.resType) 45 | } 46 | 47 | @Test 48 | fun getTagName() { 49 | Assert.assertEquals("item", remover.tagName) 50 | } 51 | 52 | @Test 53 | fun getResourceType() { 54 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/AnimFileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.AnimFileRemover 4 | import internal.filetype.FileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import util.SearchPattern 8 | 9 | /** 10 | * author: tang 11 | * created on: 2019-08-09 13:21 12 | * description: 13 | */ 14 | class AnimFileRemoverTest { 15 | private val remover: FileRemover = AnimFileRemover() 16 | 17 | @Test 18 | fun testPatternMatches() { 19 | val pattern = remover.createSearchPattern("fade_transition") 20 | var fileText = "R.anim.fade_transition\n" 21 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 22 | 23 | fileText = "R.anim.fade_transition)" 24 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 25 | 26 | fileText = "R.anim.fade_transition\"" 27 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 28 | 29 | fileText = "@anim/fade_transition<" 30 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 31 | 32 | fileText = "R.animator.fade_transition\"" 33 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 34 | 35 | fileText = "R.anim.scale_transition" 36 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 37 | 38 | fileText = "@animator/fade_transition" 39 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 40 | } 41 | 42 | @Test 43 | fun getFileType() { 44 | Assert.assertEquals("anim", remover.fileType) 45 | } 46 | 47 | @Test 48 | fun getResourceName() { 49 | Assert.assertEquals("anim", remover.resType) 50 | } 51 | 52 | @Test 53 | fun getResourceType() { 54 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/util/SearchPattern.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | /** 4 | * author: tang 5 | * created on: 2019-08-07 17:11 6 | * description: 7 | */ 8 | import org.gradle.util.GUtil.toCamelCase 9 | 10 | object SearchPattern { 11 | 12 | fun create(resType: String, resName: String, mainType: Type): String { 13 | when (mainType) { 14 | Type.STYLE -> { 15 | return """(@($resType|${resType}StateList)\/$resName["|<|.])|(R\.$resType\.${toCamelCaseWithUnderscore( 16 | resName 17 | )}[,|)|;|"|\s|:])|($resName\.)|(parent="$resName")""" 18 | } 19 | Type.DRAWABLE -> { 20 | val targetDrawable = resName.replace(".9", "") 21 | return """(@($resType|${resType}StateList)\/$targetDrawable["|<])|(R\.$resType\.$targetDrawable[,|)|;|"|\s|:| ])""" 22 | } 23 | Type.LAYOUT -> { 24 | return """(@($resType|${resType}StateList)\/$resName["|<])|(R\.$resType\.$resName[,|)|;|"|\s|:])|(${toCamelCase( 25 | resName 26 | )}Binding)""" 27 | } 28 | else -> { 29 | return """(@($resType|${resType}StateList)\/$resName["|<])|(R\.$resType\.$resName[,|)|;|"|\s|:| ])""" 30 | } 31 | } 32 | } 33 | 34 | fun toCamelCaseWithUnderscore(name: String): String { 35 | val builder = StringBuilder() 36 | builder.append(name) 37 | name.forEachIndexed { index, value -> 38 | if (value == '.') { 39 | builder.setCharAt(index, '_') 40 | if (index + 1 <= name.length) { 41 | builder.setCharAt(index + 1, name[index + 1].toUpperCase()) 42 | } 43 | } 44 | } 45 | return builder.toString() 46 | } 47 | 48 | enum class Type { 49 | STYLE, 50 | DRAWABLE, 51 | LAYOUT, 52 | DEFAULT; 53 | } 54 | } 55 | 56 | //fun main() { 57 | // println(toCamelCase("nd_ahN_ksd")) 58 | //} -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/MenuFileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.FileRemover 4 | import internal.filetype.MenuFileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import util.SearchPattern 8 | 9 | /** 10 | * author: tang 11 | * created on: 2019-08-09 15:22 12 | * description: 13 | */ 14 | class MenuFileRemoverTest { 15 | 16 | private val remover: FileRemover = MenuFileRemover() 17 | 18 | @Test 19 | fun testPatternMatches() { 20 | val pattern = remover.createSearchPattern("menu_main") 21 | var fileText = "R.menu.menu_main;" 22 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 23 | 24 | fileText = "R.menu.menu_main)" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "R.menu.menu_main)" 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "@menu/menu_main\"" 31 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 32 | 33 | fileText = "@menu/menu_main<" 34 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 35 | 36 | fileText = "R.menu.menu_detail" 37 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 38 | 39 | fileText = "@menu/menu_main2\"" 40 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 41 | 42 | fileText = "@layout/menu_main" 43 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 44 | } 45 | 46 | @Test 47 | fun getType() { 48 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 49 | } 50 | 51 | @Test 52 | fun getFileType() { 53 | Assert.assertEquals("menu", remover.fileType) 54 | } 55 | 56 | @Test 57 | fun getResourceName() { 58 | Assert.assertEquals("menu", remover.resType) 59 | } 60 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/ThemeXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang created on: 2019-08-12 13:20 description: 9 | */ 10 | class ThemeXmlValueRemoverTest { 11 | 12 | private val remover = ThemeXmlValueRemover() 13 | 14 | @Test 15 | fun testPatternMatches() { 16 | val pattern = remover.createSearchPattern("AppTheme.Translucent") 17 | 18 | var fileText = "R.style.AppTheme_Translucent;" 19 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 20 | 21 | 22 | fileText = "R.style.AppTheme_Translucent)" 23 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 24 | 25 | fileText = "R.style.AppTheme.Trans" 26 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 27 | 28 | fileText = "@style/AppTheme.Translucent\"" 29 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 30 | 31 | fileText = "@style/AppTheme.Translucent<" 32 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 33 | 34 | fileText = "@style/AppTheme.Translucent." 35 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 36 | 37 | fileText = "@style/AppTheme.Translucent2\"" 38 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 39 | 40 | } 41 | 42 | @Test 43 | fun getFileType() { 44 | Assert.assertEquals("theme", remover.fileType) 45 | } 46 | 47 | @Test 48 | fun getResourceName() { 49 | Assert.assertEquals("style", remover.resType) 50 | } 51 | 52 | @Test 53 | fun getTagName() { 54 | Assert.assertEquals("style", remover.tagName) 55 | } 56 | 57 | @Test 58 | fun getResourceType() { 59 | Assert.assertEquals(SearchPattern.Type.STYLE, remover.mainType) 60 | } 61 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/ColorFileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.ColorFileRemover 4 | import internal.filetype.FileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import util.SearchPattern 8 | 9 | /** 10 | * author: tang 11 | * created on: 2019-08-09 15:03 12 | * description: 13 | */ 14 | class ColorFileRemoverTest { 15 | 16 | private val remover: FileRemover = ColorFileRemover() 17 | 18 | @Test 19 | fun testPatternMatches() { 20 | val pattern = remover.createSearchPattern("primary") 21 | var fileText = "R.color.primary;" 22 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 23 | 24 | fileText = "R.color.primary\n" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "R.color.primary)" 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "@color/primary\"" 31 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 32 | 33 | fileText = "@color/primary<" 34 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 35 | 36 | // fileText = "@color/primary2" 37 | // Assert.assertFalse(FileRemover.isPatternMatched(fileText, pattern)) 38 | 39 | fileText = "R.color.secondary" 40 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 41 | 42 | fileText = "@style/primary\"" 43 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 44 | } 45 | 46 | @Test 47 | fun getResourceType() { 48 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 49 | } 50 | 51 | @Test 52 | fun getFileType() { 53 | Assert.assertEquals("color", remover.fileType) 54 | } 55 | 56 | @Test 57 | fun getResourceName() { 58 | Assert.assertEquals("color", remover.resType) 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/internal/filetype/FileRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.filetype 2 | 3 | import internal.AbstractRemover 4 | import util.LogUtil 5 | import util.SearchPattern 6 | import util.ThreadPoolManager 7 | import java.io.File 8 | 9 | open class FileRemover constructor( 10 | fileType: String, 11 | resourceName: String, 12 | type: SearchPattern.Type = SearchPattern.Type.DEFAULT 13 | ) : AbstractRemover(fileType, resourceName, type) { 14 | 15 | override fun removeEach(resDirFile: File, scanTargetFileTexts: String) { 16 | resDirFile.walk().forEach { it -> 17 | if (it.isDirectory && it.matchLast(fileType)) { 18 | LogUtil.info("3,check Dir: ${it.path} , prepare to removeFileIfNeed") 19 | it.walk().filter { !it.isDirectory }.forEach { 20 | removeFileIfNeed(it, scanTargetFileTexts) 21 | } 22 | } 23 | } 24 | } 25 | 26 | private fun removeFileIfNeed(file: File, scanTargetFileTexts: String): Boolean { 27 | if (isMatchedExcludeNames(file.path)) { 28 | LogUtil.yellow("4,[$fileType] Ignore checking ${file.name}") 29 | return false 30 | } 31 | 32 | val isMatched: Boolean = checkTargetTextMatches( 33 | file.nameWithoutExtension, scanTargetFileTexts 34 | ) 35 | 36 | return if (!isMatched) { 37 | LogUtil.green("4,[$fileType] ${file.name} 未使用;Remove!!") 38 | if (!dryRun) { 39 | file.delete() 40 | } 41 | true 42 | } else { 43 | false 44 | } 45 | } 46 | } 47 | 48 | fun File.matchLast(fileType: String): Boolean { 49 | // (\/${mainType}-.*$)|(\/${mainType}$) 50 | val regex = Regex("""(.*/$fileType-.*$)|(.*/$fileType$)""") 51 | // println("the regex is $regex") 52 | // println("file is $path") 53 | return this.path.matches(regex) 54 | } 55 | 56 | fun main() { 57 | val file = File("app/res/layout") 58 | print(file.matchLast("layout")) 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/DimenXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang 9 | * created on: 2019-08-12 14:47 10 | * description: 11 | */ 12 | class DimenXmlValueRemoverTest { 13 | 14 | private val remover: XmlValueRemover = DimenXmlValueRemover() 15 | 16 | @Test 17 | fun testPatternMatches() { 18 | val pattern = remover.createSearchPattern("text_medium") 19 | var fileText = "R.dimen.text_medium;" 20 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 21 | 22 | fileText = "R.dimen.text_medium," 23 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 24 | 25 | fileText = "R.dimen.text_medium)" 26 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 27 | 28 | fileText = "R.dimen.text" 29 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 30 | 31 | fileText = "@dimen/text_medium\"" 32 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 33 | 34 | fileText = "@dimen/text_medium<" 35 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 36 | 37 | fileText = "@dimen/text_medium2\"" 38 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 39 | 40 | fileText = "@style/text_medium" 41 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 42 | } 43 | 44 | @Test 45 | fun getFileType() { 46 | Assert.assertEquals("dimen", remover.fileType) 47 | } 48 | 49 | @Test 50 | fun getResourceName() { 51 | Assert.assertEquals("dimen", remover.resType) 52 | } 53 | 54 | @Test 55 | fun getTagName() { 56 | Assert.assertEquals("dimen", remover.tagName) 57 | } 58 | 59 | @Test 60 | fun getResourceType() { 61 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 62 | } 63 | } -------------------------------------------------------------------------------- /src/test/kotlin/util/DirectoryMatcherTest.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import internal.filetype.matchLast 4 | import org.junit.Assert 5 | import org.junit.Test 6 | import java.io.File 7 | 8 | /** 9 | * author: tang 10 | * created on: 2019-08-12 18:14 11 | * description: 12 | */ 13 | class DirectoryMatcherTest { 14 | 15 | @Test 16 | fun testDirMatch() { 17 | //test match mainType with the last part of dir name 18 | 19 | var dirName = "/values" 20 | var type = "values" 21 | Assert.assertTrue(File(dirName).matchLast(type)) 22 | 23 | dirName = "/values-v21" 24 | type = "values" 25 | Assert.assertTrue(File(dirName).matchLast(type)) 26 | 27 | dirName = "/drawable" 28 | type = "drawable" 29 | Assert.assertTrue(File(dirName).matchLast(type)) 30 | 31 | dirName = "xxxx/drawable-hdpi" 32 | type = "drawable" 33 | Assert.assertTrue(File(dirName).matchLast(type)) 34 | 35 | dirName = "/drawable-v21" 36 | type = "drawable" 37 | Assert.assertTrue(File(dirName).matchLast(type)) 38 | 39 | dirName = "/anim" 40 | type = "anim" 41 | Assert.assertTrue(File(dirName).matchLast(type)) 42 | 43 | dirName = "/animator" 44 | type = "animator" 45 | Assert.assertTrue(File(dirName).matchLast(type)) 46 | 47 | dirName = "/xml/shortcuts.xml" 48 | type = "xml" 49 | Assert.assertFalse(File(dirName).matchLast(type)) 50 | 51 | 52 | dirName = "/valuesdir" 53 | type = "values" 54 | Assert.assertFalse(File(dirName).matchLast(type)) 55 | 56 | dirName = "values" 57 | type = "values" 58 | Assert.assertFalse(File(dirName).matchLast(type)) 59 | 60 | dirName = "/animator" 61 | type = "anim" 62 | Assert.assertFalse(File(dirName).matchLast(type)) 63 | 64 | dirName = "/anim" 65 | type = "animator" 66 | Assert.assertFalse(File(dirName).matchLast(type)) 67 | 68 | dirName = "/values/strings.xml" 69 | type = "xml" 70 | Assert.assertFalse(File(dirName).matchLast(type)) 71 | } 72 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/DrawableFileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.DrawableFileRemover 4 | import internal.filetype.FileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import util.SearchPattern 8 | 9 | /** 10 | * author: tang 11 | * created on: 2019-08-09 15:25 12 | * description: 13 | */ 14 | class DrawableFileRemoverTest { 15 | 16 | private val remover: FileRemover = DrawableFileRemover() 17 | 18 | @Test 19 | fun testPatternMatches() { 20 | var pattern = remover.createSearchPattern("ic_settings") 21 | var fileText = "R.drawable.ic_settings\n" 22 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 23 | 24 | fileText = "R.drawable.ic_settings)" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "@drawable/ic_settings\"" 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "@drawable/ic_settings<" 31 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 32 | 33 | fileText = "R.drawable.ic_setting" 34 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 35 | 36 | fileText = "@mipmap/ic_settings" 37 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 38 | 39 | 40 | pattern = remover.createSearchPattern("img_balloon.9") 41 | 42 | fileText = "@drawable/img_balloon\"" 43 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 44 | 45 | // fileText = "@drawable/img_balloon2\"" 46 | // Assert.assertFalse(FileRemover.isPatternMatched(fileText, pattern)) 47 | } 48 | 49 | @Test 50 | fun getFileType() { 51 | Assert.assertEquals("drawable", remover.fileType) 52 | } 53 | 54 | @Test 55 | fun getResourceName() { 56 | Assert.assertEquals("drawable", remover.resType) 57 | } 58 | 59 | @Test 60 | fun getType() { 61 | Assert.assertEquals(SearchPattern.Type.DRAWABLE, remover.mainType) 62 | } 63 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/ColorXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang 9 | * created on: 2019-08-12 14:47 10 | * description: 11 | */ 12 | class ColorXmlValueRemoverTest { 13 | 14 | private val remover: XmlValueRemover = ColorXmlValueRemover() 15 | 16 | @Test 17 | fun testPatternMatches() { 18 | val pattern = remover.createSearchPattern("primary") 19 | var fileText = "R.color.primary;" 20 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 21 | fileText = "R.color.secondary" 22 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 23 | 24 | fileText = "@color/primary\"" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "@color/primary<" 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "@color/primary:" 31 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 32 | 33 | fileText = "@color/primary" 34 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 35 | 36 | 37 | fileText = "@colorStateList/primary\"" 38 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 39 | 40 | fileText = "@color/primary2\"" 41 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 42 | 43 | fileText = "@style/primary" 44 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 45 | } 46 | 47 | @Test 48 | fun getFileType() { 49 | Assert.assertEquals("color", remover.fileType) 50 | } 51 | 52 | @Test 53 | fun getResourceName() { 54 | Assert.assertEquals("color", remover.resType) 55 | } 56 | 57 | @Test 58 | fun getTagName() { 59 | Assert.assertEquals("color", remover.tagName) 60 | } 61 | 62 | @Test 63 | fun getResourceType() { 64 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 65 | } 66 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/AnimatorFileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.AnimatorFileRemover 4 | import internal.filetype.FileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import util.SearchPattern 8 | 9 | /** 10 | * author: tang 11 | * created on: 2019-08-09 12:36 12 | * description: 13 | */ 14 | class AnimatorFileRemoverTest { 15 | private val remover: FileRemover = AnimatorFileRemover() 16 | 17 | @Test 18 | fun testPatternMatches() { 19 | val pattern = remover.createSearchPattern("fade_animation") 20 | var fileText = "R.animator.fade_animation\n" 21 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 22 | 23 | // fileText = "R.animator.fade_animation" 24 | // Assert.assertTrue(FileRemover.isPatternMatched(fileText, pattern)) 25 | 26 | fileText = "R.animator.fade_animation)" 27 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 28 | 29 | fileText = "R.animator.fade_animation\"" 30 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 31 | 32 | fileText = "@animator/fade_animation\"" 33 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 34 | 35 | fileText = "@animator/fade_animation<" 36 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 37 | 38 | fileText = "@animator/fade_animation2" 39 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 40 | 41 | fileText = "R.animator.scale_animation" 42 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 43 | 44 | fileText = "@anim/fade_animation" 45 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 46 | } 47 | 48 | @Test 49 | fun getFileType() { 50 | Assert.assertEquals("animator", remover.fileType) 51 | } 52 | 53 | @Test 54 | fun getResourceName() { 55 | Assert.assertEquals("animator", remover.resType) 56 | } 57 | 58 | @Test 59 | fun getResourceType() { 60 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/StringXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang created on: 2019-08-12 13:20 description: 9 | */ 10 | class StringXmlValueRemoverTest { 11 | 12 | private val remover = StringXmlValueRemover() 13 | 14 | @Test 15 | fun testPatternMatches() { 16 | val pattern = remover.createSearchPattern("app_name") 17 | var fileText = "R.string.app_name;" 18 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 19 | 20 | fileText = "R.string.app_name)" 21 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 22 | 23 | fileText = "R.string.app_name:" 24 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 25 | 26 | fileText = "R.string.app_name," 27 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 28 | 29 | fileText = "R.string.app_name ;" 30 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 31 | 32 | fileText = "R.string.app_name\n" 33 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 34 | 35 | fileText = "@string/app_name<" 36 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 37 | 38 | fileText = "@string/app_name(" 39 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 40 | 41 | fileText = "@string/app_name)" 42 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 43 | 44 | fileText = "@string/app_name\n" 45 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 46 | 47 | fileText = "@string/app_name\"" 48 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 49 | 50 | fileText = "@string/app_name2\"" 51 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 52 | 53 | fileText = "@style/app_name" 54 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 55 | 56 | } 57 | 58 | @Test 59 | fun getFileType() { 60 | Assert.assertEquals("string", remover.fileType) 61 | } 62 | 63 | @Test 64 | fun getResourceName() { 65 | Assert.assertEquals("string", remover.resType) 66 | } 67 | 68 | @Test 69 | fun getTagName() { 70 | Assert.assertEquals("string", remover.tagName) 71 | } 72 | 73 | @Test 74 | fun getResourceType() { 75 | Assert.assertEquals(SearchPattern.Type.DEFAULT, remover.mainType) 76 | } 77 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/valuetype/StyleXmlValueRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import util.SearchPattern 6 | 7 | /** 8 | * author: tang created on: 2019-08-12 13:20 description: 9 | */ 10 | class StyleXmlValueRemoverTest { 11 | 12 | private val remover = StyleXmlValueRemover() 13 | 14 | @Test 15 | fun testPatternMatches() { 16 | val pattern = remover.createSearchPattern("TitleTextAppearance") 17 | 18 | var fileText = "R.style.TitleTextAppearance;" 19 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 20 | 21 | fileText = "R.style.TitleTextAppearance)" 22 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 23 | 24 | fileText = "R.style.TitleTextAppearance:" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "R.style.TitleTextAppearance," 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "R.style.TitleTextAppearance\n" 31 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 32 | 33 | fileText = "R.style.TitleTextAppearance ;" 34 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 35 | 36 | fileText = "R.style.TitleTextAppear," 37 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 38 | 39 | 40 | fileText = "@style/TitleTextAppearance\"" 41 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 42 | 43 | fileText = "@style/TitleTextAppearance<" 44 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 45 | 46 | fileText = "@style/TitleTextAppearance." 47 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 48 | 49 | fileText = "@style/TitleTextAppearance2\"" 50 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 51 | 52 | fileText = "@theme/TitleTextAppearance" 53 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 54 | } 55 | 56 | @Test 57 | fun getFileType() { 58 | Assert.assertEquals("style", remover.fileType) 59 | } 60 | 61 | @Test 62 | fun getResourceName() { 63 | Assert.assertEquals("style", remover.resType) 64 | } 65 | 66 | @Test 67 | fun getTagName() { 68 | Assert.assertEquals("style", remover.tagName) 69 | } 70 | 71 | @Test 72 | fun getResourceType() { 73 | Assert.assertEquals(SearchPattern.Type.STYLE, remover.mainType) 74 | } 75 | } -------------------------------------------------------------------------------- /src/test/kotlin/internal/fileType/LayoutFileRemoverTest.kt: -------------------------------------------------------------------------------- 1 | package internal.fileType 2 | 3 | import internal.filetype.FileRemover 4 | import internal.filetype.LayoutFileRemover 5 | import org.junit.Assert 6 | import org.junit.Test 7 | import util.SearchPattern 8 | 9 | /** 10 | * author: tang 11 | * created on: 2019-08-09 11:08 12 | * description: 13 | */ 14 | class LayoutFileRemoverTest { 15 | 16 | private val remover: FileRemover = LayoutFileRemover() 17 | 18 | @Test 19 | fun testPatternMatches() { 20 | //1:@layout/开头,必定以"结尾,或<结尾 如@anim/wifipay_anim_pw_push_bottom_in 21 | //2: R.layout.开头,要么接name后)结尾,要么,符号,考虑到kotlin,也有可能是换行符 22 | var pattern = remover.createSearchPattern("activity_main") 23 | 24 | var fileText = "R.layout.activity_main);getPayCa" 25 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 26 | 27 | fileText = "myLayout = R.layout.activity_main;" 28 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 29 | 30 | fileText = "myLayout = R.layout.activity_main\n" 31 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 32 | 33 | 34 | fileText = "inflater.inflate(R.layout.activity_main, null);" 35 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 36 | 37 | fileText = "inflater.inflate(R.layout.activity_main2, null);" 38 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 39 | 40 | fileText = "R.layout.fragment_main" 41 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 42 | 43 | 44 | fileText = "@layout/activity_main\"" 45 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 46 | 47 | fileText = "@layout/activity_main<" 48 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 49 | 50 | fileText = "ActivityMainBinding" 51 | Assert.assertTrue(remover.isPatternMatched(fileText, pattern)) 52 | 53 | fileText = "@layout/activity_main2\"" 54 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 55 | 56 | fileText = "@menu/activity_main" 57 | Assert.assertFalse(remover.isPatternMatched(fileText, pattern)) 58 | } 59 | 60 | @Test 61 | fun getFileType() { 62 | Assert.assertEquals("layout", remover.fileType) 63 | } 64 | 65 | @Test 66 | fun getResourceName() { 67 | Assert.assertEquals("layout", remover.resType) 68 | } 69 | 70 | @Test 71 | fun getType() { 72 | Assert.assertEquals(SearchPattern.Type.LAYOUT, remover.mainType) 73 | } 74 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/AbstractRemover.kt: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import RemoveResExt 4 | import util.LogUtil 5 | import util.SearchPattern 6 | import java.io.File 7 | import java.util.ArrayList 8 | import java.util.regex.Pattern 9 | 10 | /** 11 | * author: tang 12 | * created on: 2019-08-07 16:48 13 | * description: 14 | */ 15 | abstract class AbstractRemover( 16 | open val fileType: String, 17 | open val resType: String, 18 | open val mainType: SearchPattern.Type 19 | ) { 20 | var excludeNames: ArrayList = ArrayList() 21 | var dryRun = false 22 | 23 | abstract fun removeEach(resDirFile: File, scanTargetFileTexts: String) 24 | 25 | fun createSearchPattern(resName: String): String { 26 | // println("匹配正则: $searchPattern") 27 | return SearchPattern.create(resType, resName, mainType) 28 | } 29 | 30 | /** 31 | * 根据fileType 删除多余文件 32 | */ 33 | fun remove( 34 | moduleSrcDirs: ArrayList, 35 | scanTargetFileTexts: String, 36 | extension: RemoveResExt 37 | ) { 38 | this.dryRun = extension.dryRun 39 | this.excludeNames = extension.excludeNames 40 | 41 | //todo 兼容多渠道 42 | moduleSrcDirs.forEach { 43 | LogUtil.blue("2,Checking [$fileType] in $it") 44 | 45 | var resDirFile = File("$it/src/main/res") 46 | if (resDirFile.exists()) { 47 | removeEach(resDirFile, scanTargetFileTexts) 48 | } 49 | 50 | resDirFile = File("$it/res") 51 | if (resDirFile.exists()) { 52 | removeEach(resDirFile, scanTargetFileTexts) 53 | } 54 | } 55 | } 56 | 57 | fun checkTargetTextMatches(targetText: String, scanTargetFileTexts: String): Boolean { 58 | // LogUtil.green("检查 $fileType 类型文件是否被使用,文件名: $targetText.$fileType") 59 | val pattern = createSearchPattern(targetText) 60 | return isPatternMatched( 61 | scanTargetFileTexts, 62 | pattern 63 | ) 64 | } 65 | 66 | fun isMatchedExcludeNames(filePath: String): Boolean { 67 | // println("isMatchedExcludeNames filePath: $filePath") 68 | // println("isMatchedExcludeNames excludeNames: $excludeNames") 69 | return excludeNames.count { 70 | filePath.contains(it) 71 | } > 0 72 | } 73 | 74 | override fun toString(): String { 75 | return ("fileType: " 76 | + fileType 77 | + ", resType: " 78 | + resType 79 | + ", mainType: " 80 | + mainType.toString()) 81 | } 82 | 83 | fun isDryRun(): Boolean { 84 | return dryRun 85 | } 86 | 87 | /** 88 | * 是否能部分匹配上? 89 | */ 90 | fun isPatternMatched(fileText: String, pattern: String): Boolean { 91 | val matcher = Pattern.compile(pattern).matcher(fileText); 92 | return matcher.find() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/kotlin/internal/valuetype/XmlValueRemover.kt: -------------------------------------------------------------------------------- 1 | package internal.valuetype 2 | 3 | import internal.AbstractRemover 4 | import internal.filetype.matchLast 5 | import org.jdom2.Content 6 | import org.jdom2.Document 7 | import org.jdom2.Element 8 | import org.jdom2.Namespace 9 | import org.jdom2.Text 10 | import org.jdom2.Verifier 11 | import org.jdom2.input.SAXBuilder 12 | import org.jdom2.output.EscapeStrategy 13 | import org.jdom2.output.Format 14 | import org.jdom2.output.LineSeparator 15 | import org.jdom2.output.XMLOutputter 16 | import util.LogUtil 17 | import util.SearchPattern 18 | import util.ThreadPoolManager 19 | import java.io.File 20 | import java.io.StringWriter 21 | 22 | /** 23 | * author: tang 24 | * created on: 2019-08-11 21:08 25 | * description: 26 | */ 27 | open class XmlValueRemover constructor( 28 | /** 29 | * 资源文件名, 30 | * 如colors.xml的color 31 | * dimens.xml的dimens 32 | */ 33 | fileType: String, 34 | /** 35 | * R.xx.name的xx 36 | */ 37 | resourceName: String, 38 | /** 39 | * <`dimen` name="width"> 40 | * <`string` name="app_name"> 41 | */ 42 | open var tagName: String, 43 | type: SearchPattern.Type = SearchPattern.Type.DEFAULT 44 | ) : AbstractRemover(fileType, resourceName, type) { 45 | 46 | override fun removeEach(resDirFile: File, scanTargetFileTexts: String) { 47 | resDirFile.walk().filter { it.isDirectory && it.matchLast("values") }.forEach { 48 | // 遍历 res-values 目录下的所有文件 49 | it.walk().filter { it1 -> !it1.isDirectory && it1.exists() }.forEach { it2 -> 50 | ThreadPoolManager.instance.execute(Runnable { 51 | if (isPatternMatched(it2.name, "$fileType.*")) { 52 | removeTagIfNeed(it2, scanTargetFileTexts) 53 | } 54 | }) 55 | } 56 | } 57 | } 58 | 59 | private fun removeTagIfNeed(file: File, scanTargetFileTexts: String) { 60 | if (isMatchedExcludeNames(file.path)) { 61 | LogUtil.yellow("[" + fileType + "] Ignore checking " + file.name) 62 | return 63 | } 64 | 65 | var isFileChanged = false 66 | 67 | val doc = SAXBuilder().build(file) 68 | val iterator = doc.rootElement.content.iterator() 69 | 70 | var isAfterRemoved = false 71 | 72 | while (iterator.hasNext()) { 73 | val content = iterator.next() 74 | 75 | // Remove line break after element is removed 76 | if (isAfterRemoved && content.cType == Content.CType.Text) { 77 | val text = content as Text 78 | if (text.text.contains("\n")) { 79 | if (!dryRun) { 80 | iterator.remove() 81 | } 82 | } 83 | isAfterRemoved = false 84 | } else if (content.cType == Content.CType.Element) { 85 | val element = content as Element 86 | if (element.name == tagName) { 87 | val attr = element.getAttribute("name") 88 | 89 | if (attr != null) { 90 | // Check the element has tools:override attribute 91 | val attribute = element.getAttribute("override", TOOLS_NAMESPACE) 92 | if (attribute?.value == "true") { 93 | LogUtil.yellow( 94 | "[" 95 | + fileType 96 | + "] Skip checking " 97 | + attr.value 98 | + " which has tools:override in " 99 | + file.name 100 | ) 101 | continue 102 | } 103 | 104 | val isMatched = checkTargetTextMatches(attr.value, scanTargetFileTexts) 105 | 106 | if (!isMatched) { 107 | LogUtil.green( 108 | "[" + fileType + "] Remove " + attr.value + " in " + file.name 109 | ) 110 | if (!dryRun) { 111 | iterator.remove() 112 | } 113 | isAfterRemoved = true 114 | isFileChanged = true 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | if (isFileChanged) { 122 | if (!dryRun) { 123 | saveFile(doc, file) 124 | removeFileIfNeed(file) 125 | } 126 | } else { 127 | LogUtil.info("[" + fileType + "] No unused tags in " + file.name) 128 | } 129 | } 130 | 131 | private fun removeFileIfNeed(file: File) { 132 | val doc = SAXBuilder().build(file) 133 | if (doc.rootElement.getChildren(tagName).size == 0) { 134 | LogUtil.green("[" + fileType + "] Remove " + file.name + ".") 135 | file.delete() 136 | } 137 | } 138 | 139 | companion object { 140 | 141 | private fun saveFile(doc: Document, file: File) { 142 | val stringWriter = StringWriter() 143 | 144 | 145 | XMLOutputter().apply { 146 | format = Format.getRawFormat() 147 | format.setLineSeparator(LineSeparator.SYSTEM) 148 | format.textMode = Format.TextMode.PRESERVE 149 | format.encoding = "utf-8" 150 | format.escapeStrategy = ESCAPE_STRATEGY 151 | output(doc, stringWriter) 152 | // output(doc, new FileWriter(file)) 153 | // output(doc, System.out) 154 | } 155 | // TODO This is a temporary fix to remove extra spaces in last . 156 | file.writeText( 157 | stringWriter.toString().replaceFirst("""\n\s+<\/resources>""", """\n""") 158 | ) 159 | } 160 | 161 | private val ESCAPE_STRATEGY = EscapeStrategy { ch -> 162 | // To support 163 | Verifier.isHighSurrogate(ch) || 60 == ch.toInt().ushr(10) 164 | } 165 | private val TOOLS_NAMESPACE = 166 | Namespace.getNamespace("tools", "http://schemas.android.com/tools") 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/kotlin/RemoveResPlugin.kt: -------------------------------------------------------------------------------- 1 | import internal.filetype.AnimFileRemover 2 | import internal.filetype.AnimatorFileRemover 3 | import internal.filetype.ColorFileRemover 4 | import internal.filetype.DrawableFileRemover 5 | import internal.filetype.LayoutFileRemover 6 | import internal.filetype.MenuFileRemover 7 | import internal.filetype.MipmapFileRemover 8 | import internal.valuetype.AttrXmlValueRemover 9 | import internal.valuetype.BoolXmlValueRemover 10 | import internal.valuetype.ColorXmlValueRemover 11 | import internal.valuetype.DimenXmlValueRemover 12 | import internal.valuetype.IdXmlValueRemover 13 | import internal.valuetype.IntegerXmlValueRemover 14 | import internal.valuetype.StringXmlValueRemover 15 | import internal.valuetype.StyleXmlValueRemover 16 | import internal.valuetype.ThemeXmlValueRemover 17 | import org.gradle.api.Plugin 18 | import org.gradle.api.Project 19 | import util.LogUtil 20 | import util.ThreadPoolManager 21 | import java.io.File 22 | 23 | /** 24 | * author: tang 25 | * created on: 2019-08-07 14:05 26 | * description: 27 | */ 28 | open class RemoveResPlugin : Plugin { 29 | 30 | override fun apply(project: Project) { 31 | 32 | if (project != project.rootProject) { 33 | LogUtil.info("RemoveResPlugin do nothing in ${project.displayName}") 34 | return 35 | } else { 36 | LogUtil.info("RemoveResPlugin begin in ${project.displayName}") 37 | } 38 | 39 | project.extensions.create(RemoveResExt.name, RemoveResExt::class.java) 40 | 41 | val moduleSrcDirs = getModuleSrcDirs(project) 42 | // LogUtil.yellow("全部模块: $moduleSrcDirs") 43 | 44 | val scanTargetFileTexts = createScanTargetFileTexts(moduleSrcDirs) 45 | // LogUtil.blue("createScanTargetFileTexts ok") 46 | 47 | val fileRemoverList = ArrayList( 48 | listOf( 49 | LayoutFileRemover(), MenuFileRemover(), 50 | MipmapFileRemover(), 51 | DrawableFileRemover(), AnimatorFileRemover(), 52 | AnimFileRemover(), 53 | ColorFileRemover() 54 | ) 55 | ) 56 | 57 | val valueRemoverList = ArrayList( 58 | listOf( 59 | AttrXmlValueRemover(), BoolXmlValueRemover(), 60 | ColorXmlValueRemover(), 61 | DimenXmlValueRemover(), IdXmlValueRemover(), 62 | IntegerXmlValueRemover(), 63 | StringXmlValueRemover(), StyleXmlValueRemover(), ThemeXmlValueRemover() 64 | ) 65 | ) 66 | 67 | val extension: RemoveResExt = project.extensions.findByName(RemoveResExt.name) as RemoveResExt 68 | 69 | logOpen = extension.logOpen 70 | 71 | logExtensionInfo(extension) 72 | 73 | project.task("RemoveRes").doLast { 74 | 75 | LogUtil.blue("this is RemoveResPlugin,dealing with ${project.name}") 76 | 77 | // Remove unused files 78 | if (extension.openRemoveFile) { 79 | fileRemoverList.forEach { 80 | LogUtil.green("doing FileRemover: $it in ${project.name}") 81 | val fileRemoverThread = Runnable { 82 | it.remove(moduleSrcDirs, scanTargetFileTexts, extension) 83 | } 84 | ThreadPoolManager.instance.execute(fileRemoverThread) 85 | } 86 | } 87 | 88 | //Remove unused xml values 89 | if (extension.openRemoveXmlValues) { 90 | valueRemoverList.forEach { 91 | LogUtil.green("doing XmlRemover: $it in ${project.name}") 92 | val valueRemoverThread = Runnable { 93 | it.remove(moduleSrcDirs, scanTargetFileTexts, extension) 94 | } 95 | ThreadPoolManager.instance.execute(valueRemoverThread) 96 | } 97 | } 98 | 99 | while (!ThreadPoolManager.instance.isOver) { 100 | LogUtil.blue("${ThreadPoolManager.instance}") 101 | Thread.sleep(3000) 102 | } 103 | } 104 | } 105 | 106 | companion object { 107 | 108 | var logOpen = false 109 | 110 | const val string = "" 111 | val moduleSrcDir = ArrayList() 112 | 113 | fun logExtensionInfo(extension: RemoveResExt) { 114 | if (extension.extraRemovers.isNotEmpty()) { 115 | LogUtil.info("extraRemovers:") 116 | extension.extraRemovers.forEach { 117 | LogUtil.info(" $it") 118 | } 119 | } 120 | 121 | if (extension.excludeNames.isNotEmpty()) { 122 | LogUtil.info("excludeNames:") 123 | extension.excludeNames.forEach { 124 | LogUtil.info(" $it") 125 | } 126 | } 127 | LogUtil.info("dryRun: " + extension.dryRun.toString()) 128 | } 129 | } 130 | 131 | private fun getModuleSrcDirs(project: Project): ArrayList { 132 | return if (moduleSrcDir.isEmpty()) { 133 | project.rootProject.allprojects.filter { 134 | it.name != project.rootProject.name 135 | }.map { 136 | it.projectDir.path 137 | } as ArrayList 138 | } else { 139 | moduleSrcDir 140 | } 141 | } 142 | 143 | var string = "" 144 | 145 | /** 146 | * 拼接需要查找的文件中的所有text内容 147 | */ 148 | private fun createScanTargetFileTexts(moduleSrcDirs: List): String { 149 | 150 | if (string.isNotEmpty()) { 151 | return string 152 | } 153 | 154 | val stringBuilder = StringBuilder() 155 | 156 | moduleSrcDirs.map { File(it) }.forEach { srcDirFile -> 157 | srcDirFile.walk().forEach { 158 | // println("deal with file ${it.name} , prepare to filter") 159 | if (it.name.matches( 160 | Regex( 161 | """(.*\.xml)|(.*\.kt)|(.*\.java)|(.*\.gradle)""" 162 | ) 163 | ) 164 | ) { 165 | stringBuilder.append(it.readText()) 166 | } 167 | } 168 | } 169 | return stringBuilder.toString() 170 | } 171 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | --------------------------------------------------------------------------------