diff --git a/git_code_debt/metrics/common.py b/git_code_debt/metrics/common.py
index 8cf025f..d91f272 100644
--- a/git_code_debt/metrics/common.py
+++ b/git_code_debt/metrics/common.py
@@ -1,56 +1,12 @@
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
-
-PYTHON = 'Python'
-YAML = 'Yaml'
-TEMPLATE = 'Template'
-CSS = 'Css'
-MAKO_TEMPLATE = 'Mako_Template'
-JAVASCRIPT = 'Javascript'
-JAVA = 'Java'
-ILLUSTRATOR = 'Illustrator'
-HTML = 'Html'
-CCPP = 'C_C++'
-TEXT = 'Text'
-SQL = 'SQL'
-
-
-# Maps a set of file extensions to a nice name.
-# Updating this will cause that file type to be tracked for LinesOfCode metric.
-FILE_TYPE_MAP = {
-    b'.py': PYTHON,
-
-    b'.yaml': YAML,
-    b'.yml': YAML,
-
-    b'.css': CSS,
-    b'.scss': CSS,
-
-    b'.tmpl': TEMPLATE,
-
-    b'.mako': MAKO_TEMPLATE,
-
-    b'.js': JAVASCRIPT,
-
-    b'.java': JAVA,
-
-    b'.ai': ILLUSTRATOR,
-
-    b'.htm': HTML,
-    b'.html': HTML,
-
-    b'.h': CCPP,
-    b'.c': CCPP,
-    b'.cpp': CCPP,
-
-    b'.md': TEXT,
-    b'.rst': TEXT,
-    b'.csv': TEXT,
-    b'.log': TEXT,
-    b'.json': TEXT,
-    b'.xml': TEXT,
-    b'.txt': TEXT,
-
-    b'.sql': SQL,
-}
+from identify import identify
+
+UNKNOWN = 'unknown'
+IGNORED_TAGS = frozenset((
+    identify.DIRECTORY, identify.SYMLINK, identify.FILE,
+    identify.EXECUTABLE, identify.NON_EXECUTABLE,
+    identify.TEXT, identify.BINARY,
+))
+ALL_TAGS = frozenset((identify.ALL_TAGS - IGNORED_TAGS) | {UNKNOWN})
diff --git a/git_code_debt/metrics/curse.py b/git_code_debt/metrics/curse.py
index 89a6fd0..a522169 100644
--- a/git_code_debt/metrics/curse.py
+++ b/git_code_debt/metrics/curse.py
@@ -3,9 +3,12 @@ from __future__ import unicode_literals
 
 import collections
 
+from identify import identify
+
 from git_code_debt.metric import Metric
 from git_code_debt.metrics.base import DiffParserBase
-from git_code_debt.metrics.common import FILE_TYPE_MAP
+from git_code_debt.metrics.common import ALL_TAGS
+from git_code_debt.metrics.common import UNKNOWN
 from git_code_debt.metrics.curse_words import word_list
 
 
@@ -34,20 +37,19 @@ class CurseWordsParser(DiffParserBase):
             total_curses = total_curses + curses_changed
 
             # Track by file extension -> type mapping
-            file_type = FILE_TYPE_MAP.get(file_diff_stat.extension, 'unknown')
-            curses_by_file_type[file_type] += curses_changed
+            filename = file_diff_stat.filename.decode('UTF-8')
+            tags = identify.tags_from_filename(filename) or {UNKNOWN}
+
+            for tag in tags:
+                curses_by_file_type[tag] += curses_changed
 
         # Yield overall metric and one per type of expected mapping types
         yield Metric('TotalCurseWords', total_curses)
-        for file_type in set(FILE_TYPE_MAP.values()) | {'unknown'}:
-            curses_changed = curses_by_file_type.get(file_type, 0)
-            yield Metric(
-                'TotalCurseWords_{}'.format(file_type),
-                curses_changed,
-            )
+        for tag in ALL_TAGS:
+            curses_changed = curses_by_file_type[tag]
+            yield Metric('TotalCurseWords_{}'.format(tag), curses_changed)
 
     def get_possible_metric_ids(self):
         return ['TotalCurseWords'] + [
-            'TotalCurseWords_{}'.format(file_type)
-            for file_type in set(FILE_TYPE_MAP.values()) | {'unknown'}
+            'TotalCurseWords_{}'.format(tag) for tag in ALL_TAGS
         ]
diff --git a/git_code_debt/metrics/lines.py b/git_code_debt/metrics/lines.py
index 5c082aa..5cb72ff 100644
--- a/git_code_debt/metrics/lines.py
+++ b/git_code_debt/metrics/lines.py
@@ -3,9 +3,12 @@ from __future__ import unicode_literals
 
 import collections
 
+from identify import identify
+
 from git_code_debt.metric import Metric
 from git_code_debt.metrics.base import DiffParserBase
-from git_code_debt.metrics.common import FILE_TYPE_MAP
+from git_code_debt.metrics.common import ALL_TAGS
+from git_code_debt.metrics.common import UNKNOWN
 
 
 class LinesOfCodeParser(DiffParserBase):
@@ -24,21 +27,19 @@ class LinesOfCodeParser(DiffParserBase):
             # Track total overall
             total_lines += lines_changed
 
-            # Track by file extension -> type mapping
-            file_type = FILE_TYPE_MAP.get(file_diff_stat.extension, 'unknown')
-            lines_by_file_type[file_type] += lines_changed
+            filename = file_diff_stat.filename.decode('UTF-8')
+            tags = identify.tags_from_filename(filename) or {UNKNOWN}
+
+            for tag in tags:
+                lines_by_file_type[tag] += lines_changed
 
         # Yield overall metric and one per type of expected mapping types
         yield Metric('TotalLinesOfCode', total_lines)
-        for file_type in set(FILE_TYPE_MAP.values()) | {'unknown'}:
-            lines_changed = lines_by_file_type.get(file_type, 0)
-            yield Metric(
-                'TotalLinesOfCode_{}'.format(file_type),
-                lines_changed,
-            )
+        for tag in ALL_TAGS:
+            lines_changed = lines_by_file_type[tag]
+            yield Metric('TotalLinesOfCode_{}'.format(tag), lines_changed)
 
     def get_possible_metric_ids(self):
         return ['TotalLinesOfCode'] + [
-            'TotalLinesOfCode_{}'.format(file_type)
-            for file_type in set(FILE_TYPE_MAP.values()) | {'unknown'}
+            'TotalLinesOfCode_{}'.format(tag) for tag in ALL_TAGS
         ]
diff --git a/metric_config.yaml b/metric_config.yaml
index fd1ff4e..7e3dbe3 100644
--- a/metric_config.yaml
+++ b/metric_config.yaml
@@ -14,13 +14,9 @@
 # NOTE: metrics and metric_expressions may be omitted
 
 Groups:
-    - Cheetah:
-        metrics: ['TotalLinesOfCode_Template']
-        metric_expressions:
-            - ^.*Cheetah.*$
     - Python:
         metric_expressions:
-            - ^.*Python.*$
+            - (?i)^.*Python.*$
     - CurseWords:
         metric_expressions:
             - ^TotalCurseWords.*$
@@ -50,8 +46,8 @@ CommitLinks:
 # These denote the metrics to show in the widget.
 WidgetMetrics:
     TotalLinesOfCode: {}
-    TotalLinesOfCode_Css: {}
-    TotalLinesOfCode_Python: {}
-    TotalLinesOfCode_Javascript: {}
-    TotalLinesOfCode_Text: {}
-    TotalLinesOfCode_Yaml: {}
+    TotalLinesOfCode_css: {}
+    TotalLinesOfCode_python: {}
+    TotalLinesOfCode_javascript: {}
+    TotalLinesOfCode_plain-text: {}
+    TotalLinesOfCode_yaml: {}
diff --git a/setup.py b/setup.py
index be888de..6deb81b 100644
--- a/setup.py
+++ b/setup.py
@@ -35,6 +35,7 @@ setup(
     },
     install_requires=[
         'flask',
+        'identify',
         'jsonschema',
         'mako',
         'pyyaml',

Use `identify` for code type identification
Notably https://github.com/chriskuehl/identify/blob/39c020afff92d50d6ffb8b251f5bdc30d51ca96c/identify/identify.py#L69-L86
diff --git a/tests/metrics/curse_test.py b/tests/metrics/curse_test.py
index 4b5cafc..024461a 100644
--- a/tests/metrics/curse_test.py
+++ b/tests/metrics/curse_test.py
@@ -11,8 +11,8 @@ def test_curse_words_parser():
     parser = CurseWordsParser()
     input_stats = [
         FileDiffStat(
-            b'templates/foo.tmpl',
-            [b'#man seriously, fuck cheetah'],
+            b'some/file.rb',
+            [b'#man seriously, fuck ruby'],
             [],
             None,
         ),
@@ -24,5 +24,5 @@ def test_curse_words_parser():
         ),
     ]
     metrics = list(parser.get_metrics_from_stat(Commit.blank, input_stats))
-    assert Metric('TotalCurseWords_Template', 1) in metrics
-    assert Metric('TotalCurseWords_Python', 0) in metrics
+    assert Metric('TotalCurseWords_ruby', 1) in metrics
+    assert Metric('TotalCurseWords_python', 0) in metrics
diff --git a/tests/metrics/lines_test.py b/tests/metrics/lines_test.py
index 3728981..801f2a7 100644
--- a/tests/metrics/lines_test.py
+++ b/tests/metrics/lines_test.py
@@ -17,8 +17,8 @@ def test_lines_of_code_parser():
 
     expected_value = {
         'TotalLinesOfCode': 3,
-        'TotalLinesOfCode_Python': 1,
-        'TotalLinesOfCode_Yaml': 2,
+        'TotalLinesOfCode_python': 1,
+        'TotalLinesOfCode_yaml': 2,
     }
     for metric in metrics:
         assert metric.value == expected_value.get(metric.name, 0)
diff --git a/tests/server/servlets/widget_test.py b/tests/server/servlets/widget_test.py
index 1002017..aff0423 100644
--- a/tests/server/servlets/widget_test.py
+++ b/tests/server/servlets/widget_test.py
@@ -47,11 +47,11 @@ def test_widget_data(server):
 
 def test_widget_data_multiple_values(server):
     with metrics_enabled(
-        {'TotalLinesOfCode': {}, 'TotalLinesOfCode_Text': {}},
+        {'TotalLinesOfCode': {}, 'TotalLinesOfCode_plain-text': {}},
     ):
         response = server.client.post(
             flask.url_for('widget.data'),
             data={'diff': file_diff_stat_test.SAMPLE_OUTPUT},
         )
     response_pq = pyquery.PyQuery(response.json['metrics'])
-    assert 'TotalLinesOfCode_Text' in response_pq.text()
+    assert 'TotalLinesOfCode_plain-text' in response_pq.text()

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.6
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
cfgv==3.3.1
click==8.0.4
coverage==6.2
cssselect==1.1.0
dataclasses==0.8
distlib==0.3.9
filelock==3.4.1
Flask==2.0.3
-e git+https://github.com/asottile/git-code-debt.git@6d3c4a0d3e5dd36482c010462e2b00e944e7a876#egg=git_code_debt
identify==2.4.4
importlib-metadata==4.8.3
importlib-resources==5.2.3
iniconfig==1.1.1
itsdangerous==2.0.1
Jinja2==3.0.3
jsonschema==3.2.0
lxml==5.3.1
Mako==1.1.6
MarkupSafe==2.0.1
mock==5.2.0
nodeenv==1.6.0
packaging==21.3
platformdirs==2.4.0
pluggy==1.0.0
pre-commit==2.17.0
py==1.11.0
pyparsing==3.1.4
pyquery==1.4.3
pyrsistent==0.18.0
pytest==7.0.1
pytest-env==0.6.2
PyYAML==6.0.1
six==1.17.0
toml==0.10.2
tomli==1.2.3
typing_extensions==4.1.1
virtualenv==20.16.2
Werkzeug==2.0.3
zipp==3.6.0

name: git-code-debt
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - cfgv==3.3.1
      - click==8.0.4
      - coverage==6.2
      - cssselect==1.1.0
      - dataclasses==0.8
      - distlib==0.3.9
      - filelock==3.4.1
      - flask==2.0.3
      - identify==2.4.4
      - importlib-metadata==4.8.3
      - importlib-resources==5.2.3
      - iniconfig==1.1.1
      - itsdangerous==2.0.1
      - jinja2==3.0.3
      - jsonschema==3.2.0
      - lxml==5.3.1
      - mako==1.1.6
      - markupsafe==2.0.1
      - mock==5.2.0
      - nodeenv==1.6.0
      - packaging==21.3
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pre-commit==2.17.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pyquery==1.4.3
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-env==0.6.2
      - pyyaml==6.0.1
      - six==1.17.0
      - toml==0.10.2
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - virtualenv==20.16.2
      - werkzeug==2.0.3
      - zipp==3.6.0
prefix: /opt/conda/envs/git-code-debt

tests/metrics/curse_test.py::test_curse_words_parser tests/metrics/lines_test.py::test_lines_of_code_parser
tests/server/servlets/widget_test.py::test_widget_data tests/server/servlets/widget_test.py::test_widget_data_multiple_values
tests/server/servlets/widget_test.py::test_widget_frame_loads
git_code_debt/metrics/curse.py git_code_debt/metrics/common.py setup.py git_code_debt/metrics/lines.py metric_config.yaml
git_code_debt/metrics/curse.py git_code_debt/metrics/common.py setup.py git_code_debt/metrics/lines.py metric_config.yaml