← back to dmccreary__it-management-graph

Function bodies 58 total

All specs Real LLM only Function bodies
on_page_context function · python · L31-L35 (5 LOC)
plugins/social_override.py
def on_page_context(context, page, config, **kwargs):
    """Stash the custom image path on the page for the post_page hook."""
    if page.meta and 'image' in page.meta:
        page.custom_image = page.meta['image']
    return context
on_post_page function · python · L38-L66 (29 LOC)
plugins/social_override.py
def on_post_page(html, page, config, **kwargs):
    """Replace the social plugin's generated og:image / twitter:image
    tags with the page's custom cover image URL."""
    if not hasattr(page, 'custom_image'):
        return html

    site_url = config['site_url'].rstrip('/')
    image_path = '/' + page.custom_image.lstrip('/')
    full_image_url = site_url + image_path

    # og:image uses property=
    og_pattern = re.compile(r'<meta\s+property="og:image"[^>]*?>')
    for tag in og_pattern.findall(html):
        if '/assets/images/social/' in tag:
            new_tag = f'<meta property="og:image" content="{full_image_url}">'
            html = html.replace(tag, new_tag)

    # Material emits twitter:image with property= (not name=), so match both
    twitter_pattern = re.compile(
        r'<meta\s+(?:property|name)="twitter:image"[^>]*?>'
    )
    for tag in twitter_pattern.findall(html):
        if '/assets/images/social/' in tag:
            # Preserve whichever attribute style 
init function · javascript · L10-L37 (28 LOC)
skills/it-graph-generator/assets/template-script.js
async function init() {
    try {
        // Load the graph data
        const response = await fetch('{{DATA_FILE}}');
        const data = await response.json();

        allNodes = data.nodes || [];
        allEdges = data.edges || [];

        // Extract unique node types
        allNodes.forEach(node => {
            if (node.type) nodeTypes.add(node.type);
        });

        // Initialize UI components
        initializeFilterCheckboxes();
        initializeLegend();
        initializeNetwork();
        setupEventListeners();

    } catch (error) {
        console.error('Error loading graph data:', error);
        document.getElementById('network').innerHTML =
            '<div style="padding: 20px; text-align: center; color: #ef4444;">' +
            'Error loading graph data. Please check the console for details.' +
            '</div>';
    }
}
initializeFilterCheckboxes function · javascript · L39-L60 (22 LOC)
skills/it-graph-generator/assets/template-script.js
function initializeFilterCheckboxes() {
    const container = document.getElementById('filterCheckboxes');
    container.innerHTML = '';

    Array.from(nodeTypes).sort().forEach(type => {
        const label = document.createElement('label');
        label.className = 'filter-checkbox';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.checked = true;
        checkbox.value = type;
        checkbox.onchange = applyFilters;

        const span = document.createElement('span');
        span.textContent = type;

        label.appendChild(checkbox);
        label.appendChild(span);
        container.appendChild(label);
    });
}
initializeLegend function · javascript · L62-L100 (39 LOC)
skills/it-graph-generator/assets/template-script.js
function initializeLegend() {
    const legendContent = document.getElementById('legendContent');
    legendContent.innerHTML = '';

    // Group nodes by type and get color/shape info
    const typeInfo = {};
    allNodes.forEach(node => {
        if (!typeInfo[node.type]) {
            typeInfo[node.type] = {
                color: node.color || '#94a3b8',
                shape: node.shape || 'dot',
                icon: node.icon || null
            };
        }
    });

    // Create legend items
    Object.keys(typeInfo).sort().forEach(type => {
        const item = document.createElement('div');
        item.className = 'legend-item';

        const indicator = document.createElement('div');
        indicator.className = 'legend-indicator';

        if (typeInfo[type].icon) {
            indicator.innerHTML = `<img src="${typeInfo[type].icon}" style="width: 20px; height: 20px;" alt="${type}">`;
        } else {
            indicator.style.backgroundColor = typeInfo[type].color;
 
initializeNetwork function · javascript · L102-L169 (68 LOC)
skills/it-graph-generator/assets/template-script.js
function initializeNetwork() {
    // Create vis.js DataSets
    nodes = new vis.DataSet(allNodes);
    edges = new vis.DataSet(allEdges);

    const container = document.getElementById('network');
    const data = { nodes, edges };

    const options = {
        nodes: {
            font: { size: 14, color: '#1e293b' },
            borderWidth: 2,
            borderWidthSelected: 4,
            shadow: true
        },
        edges: {
            width: 2,
            shadow: true,
            smooth: {
                type: 'continuous'
            }
        },
        physics: {
            enabled: true,
            stabilization: {
                iterations: 200
            },
            barnesHut: {
                gravitationalConstant: -4000,
                centralGravity: 0.3,
                springLength: 150,
                springConstant: 0.04,
                damping: 0.09,
                avoidOverlap: 0.5
            }
        },
        interaction: {
            ho
createTooltip function · javascript · L171-L180 (10 LOC)
skills/it-graph-generator/assets/template-script.js
function createTooltip(node) {
    let tooltip = `Type: ${node.type}\n`;
    if (node.properties) {
        tooltip += '\nProperties:\n';
        Object.entries(node.properties).forEach(([key, value]) => {
            tooltip += `  ${key}: ${value}\n`;
        });
    }
    return tooltip;
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
displayNodeDetails function · javascript · L182-L209 (28 LOC)
skills/it-graph-generator/assets/template-script.js
function displayNodeDetails(nodeData) {
    if (!nodeData) return;

    const detailsDiv = document.getElementById('nodeDetails');

    let html = `
        <div class="node-type">:${nodeData.type}</div>
        <div class="node-label">${nodeData.label}</div>
    `;

    if (nodeData.properties) {
        html += '<div class="properties-list">';
        Object.entries(nodeData.properties).forEach(([key, value]) => {
            html += `
                <div class="property-item">
                    <span class="property-key">${key}:</span>
                    <span class="property-value">${value}</span>
                </div>
            `;
        });
        html += '</div>';

        const propCount = Object.keys(nodeData.properties).length;
        html += `<div class="property-count">${propCount} ${propCount === 1 ? 'property' : 'properties'}</div>`;
    }

    detailsDiv.innerHTML = html;
}
applyFilters function · javascript · L211-L230 (20 LOC)
skills/it-graph-generator/assets/template-script.js
function applyFilters() {
    const checkboxes = document.querySelectorAll('#filterCheckboxes input[type="checkbox"]');
    const selectedTypes = Array.from(checkboxes)
        .filter(cb => cb.checked)
        .map(cb => cb.value);

    // Update nodes visibility
    allNodes.forEach(node => {
        const shouldShow = selectedTypes.includes(node.type);
        nodes.update({ id: node.id, hidden: !shouldShow });
    });

    // Update edges visibility based on visible nodes
    allEdges.forEach(edge => {
        const fromNode = nodes.get(edge.from);
        const toNode = nodes.get(edge.to);
        const shouldShow = !fromNode.hidden && !toNode.hidden;
        edges.update({ id: edge.id, hidden: !shouldShow });
    });
}
searchNodes function · javascript · L232-L271 (40 LOC)
skills/it-graph-generator/assets/template-script.js
function searchNodes() {
    const searchTerm = document.getElementById('searchInput').value.toLowerCase();

    if (!searchTerm) {
        // Reset view
        network.fit();
        return;
    }

    // Find matching nodes
    const matchingNodes = allNodes.filter(node => {
        // Search in label
        if (node.label.toLowerCase().includes(searchTerm)) return true;

        // Search in properties
        if (node.properties) {
            return Object.values(node.properties).some(value =>
                String(value).toLowerCase().includes(searchTerm)
            );
        }

        return false;
    });

    if (matchingNodes.length > 0) {
        // Focus on the first matching node
        const nodeId = matchingNodes[0].id;
        network.focus(nodeId, {
            scale: 1.5,
            animation: {
                duration: 1000,
                easingFunction: 'easeInOutQuad'
            }
        });
        network.selectNodes([nodeId]);
        displayNodeDet
selectAllFilters function · javascript · L273-L277 (5 LOC)
skills/it-graph-generator/assets/template-script.js
function selectAllFilters() {
    const checkboxes = document.querySelectorAll('#filterCheckboxes input[type="checkbox"]');
    checkboxes.forEach(cb => cb.checked = true);
    applyFilters();
}
unselectAllFilters function · javascript · L279-L283 (5 LOC)
skills/it-graph-generator/assets/template-script.js
function unselectAllFilters() {
    const checkboxes = document.querySelectorAll('#filterCheckboxes input[type="checkbox"]');
    checkboxes.forEach(cb => cb.checked = false);
    applyFilters();
}
setupEventListeners function · javascript · L285-L292 (8 LOC)
skills/it-graph-generator/assets/template-script.js
function setupEventListeners() {
    // Enter key for search
    document.getElementById('searchInput').addEventListener('keypress', function(e) {
        if (e.key === 'Enter') {
            searchNodes();
        }
    });
}
download_icons function · python · L57-L118 (62 LOC)
src/get-free-network-icons/download-icons-working.py
def download_icons():
    """Download all icons with proper error handling and rate limiting"""
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    success_count = 0
    fail_count = 0
    failed_icons = []

    print(f"Starting download of {len(icons)} icons...")
    print(f"Output directory: {OUTPUT_DIR}")
    print(f"Rate limit: {DELAY_BETWEEN_REQUESTS}s between requests\n")

    for i, (url, fname) in enumerate(icons, 1):
        print(f"[{i}/{len(icons)}] Downloading {fname}...", end=" ")

        try:
            # Make the request
            r = requests.get(url, headers=headers, timeout=10)

            if r.ok:
                # Save the file
                filepath = os.path.join(OUTPUT_DIR, fname)
                with open(filepath, "wb") as f:
                    f.write(r.content)

                size_kb = len(r.content) / 1024
                print(f"✅ ({size_kb:.1f} KB)")
                success_count += 1
            else:
                print(f"❌ HTTP {r.status_code}")
download_icons function · python · L102-L165 (64 LOC)
src/get-free-network-icons/download-it-device-icons.py
def download_icons():
    """Download IT device and management icons"""
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    success_count = 0
    fail_count = 0
    failed_icons = []

    print(f"🔽 Downloading {len(all_icons)} IT device & management icons...")
    print(f"📁 Output directory: {OUTPUT_DIR}")
    print(f"⏱️  Rate limit: {DELAY_BETWEEN_REQUESTS}s between requests")
    print(f"📜 License: MIT (Bootstrap Icons) + CC0 (Simple Icons)\n")

    for i, (url, fname) in enumerate(all_icons, 1):
        print(f"[{i:2d}/{len(all_icons)}] {fname:30s}", end=" ")

        try:
            r = requests.get(url, headers=headers, timeout=10)

            if r.ok:
                filepath = os.path.join(OUTPUT_DIR, fname)
                with open(filepath, "wb") as f:
                    f.write(r.content)

                size_kb = len(r.content) / 1024
                print(f"✅ ({size_kb:4.1f} KB)")
                success_count += 1
            else:
                print(f"❌ HTTP {r.status
All rows scored by the Repobility analyzer (https://repobility.com)
download_icons function · python · L67-L129 (63 LOC)
src/get-free-network-icons/download-network-diagram-icons.py
def download_icons():
    """Download network diagram icons"""
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    success_count = 0
    fail_count = 0
    failed_icons = []

    print(f"🔽 Downloading {len(icons)} network diagram icons...")
    print(f"📁 Output directory: {OUTPUT_DIR}")
    print(f"⏱️  Rate limit: {DELAY_BETWEEN_REQUESTS}s between requests")
    print(f"📜 License: Font Awesome Free License + Simple Icons CC0\n")

    for i, (url, fname) in enumerate(icons, 1):
        print(f"[{i:2d}/{len(icons)}] {fname:25s}", end=" ")

        try:
            r = requests.get(url, headers=headers, timeout=10)

            if r.ok:
                filepath = os.path.join(OUTPUT_DIR, fname)
                with open(filepath, "wb") as f:
                    f.write(r.content)

                size_kb = len(r.content) / 1024
                print(f"✅ ({size_kb:4.1f} KB)")
                success_count += 1
            else:
                print(f"❌ HTTP {r.status_code}")
                f
download_icons function · python · L103-L164 (62 LOC)
src/get-free-network-icons/download-network-icons.py
def download_icons():
    """Download all icons with error handling and rate limiting"""
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    success_count = 0
    fail_count = 0
    failed_icons = []

    print(f"🔽 Downloading {len(icons)} IT infrastructure icons...")
    print(f"📁 Output directory: {OUTPUT_DIR}")
    print(f"⏱️  Rate limit: {DELAY_BETWEEN_REQUESTS}s between requests")
    print(f"📜 License: CC0 (Public Domain) from Simple Icons\n")

    for i, (url, fname) in enumerate(icons, 1):
        print(f"[{i:2d}/{len(icons)}] {fname:25s}", end=" ")

        try:
            r = requests.get(url, headers=headers, timeout=10)

            if r.ok:
                filepath = os.path.join(OUTPUT_DIR, fname)
                with open(filepath, "wb") as f:
                    f.write(r.content)

                size_kb = len(r.content) / 1024
                print(f"✅ ({size_kb:4.1f} KB)")
                success_count += 1
            else:
                print(f"❌ HTTP {r.status_code
TextbookAnalyzer class · python · L10-L278 (269 LOC)
src/site-metrics/advanced-metrics.py
class TextbookAnalyzer:
    """Analyzes the quality metrics of an intelligent textbook built with mkdocs-material."""
    
    def __init__(self, repo_path: str):
        self.repo_path = Path(repo_path)
        self.docs_path = self.repo_path / 'docs'
        self.mkdocs_path = self.repo_path / 'mkdocs.yml'

    def analyze(self) -> Dict[str, Any]:
        """Performs comprehensive analysis of the textbook."""
        metrics = {
            'basic_metrics': self._get_basic_metrics(),
            'content_structure': self._analyze_content_structure(),
            'engagement_features': self._analyze_engagement_features(),
            'technical_quality': self._analyze_technical_quality()
        }
        return metrics

    def _count_words(self) -> int:
        """Counts words in markdown files, excluding code blocks and metadata."""
        if not self.docs_path.exists():
            print(f"Warning: Docs directory not found at {self.docs_path}")
            return 0
        
     
__init__ method · python · L13-L16 (4 LOC)
src/site-metrics/advanced-metrics.py
    def __init__(self, repo_path: str):
        self.repo_path = Path(repo_path)
        self.docs_path = self.repo_path / 'docs'
        self.mkdocs_path = self.repo_path / 'mkdocs.yml'
analyze method · python · L18-L26 (9 LOC)
src/site-metrics/advanced-metrics.py
    def analyze(self) -> Dict[str, Any]:
        """Performs comprehensive analysis of the textbook."""
        metrics = {
            'basic_metrics': self._get_basic_metrics(),
            'content_structure': self._analyze_content_structure(),
            'engagement_features': self._analyze_engagement_features(),
            'technical_quality': self._analyze_technical_quality()
        }
        return metrics
_count_words method · python · L28-L64 (37 LOC)
src/site-metrics/advanced-metrics.py
    def _count_words(self) -> int:
        """Counts words in markdown files, excluding code blocks and metadata."""
        if not self.docs_path.exists():
            print(f"Warning: Docs directory not found at {self.docs_path}")
            return 0
        
        total_words = 0
        md_files = list(self.docs_path.glob('**/*.md'))
        
        for md_file in md_files:
            try:
                with open(md_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                    
                    # Process content to remove non-word elements
                    # Remove YAML front matter
                    content = re.sub(r'\A---[\s\S]*?---', '', content)
                    # Remove code blocks
                    content = re.sub(r'```[\s\S]*?```', '', content)
                    # Remove inline code
                    content = re.sub(r'`[^`]*`', '', content)
                    # Remove HTML tags
                    content = re.sub(r
_count_microsims method · python · L66-L75 (10 LOC)
src/site-metrics/advanced-metrics.py
    def _count_microsims(self) -> int:
        """Counts MicroSim implementations."""
        sims_dir = self.docs_path / 'sims'
        try:
            if not sims_dir.exists():
                return 0
            return len([d for d in sims_dir.glob('*') if d.is_dir() and not d.name.startswith('_')])
        except Exception as e:
            print(f"Warning: Error counting microsims: {str(e)}")
            return 0
_count_glossary_terms method · python · L77-L89 (13 LOC)
src/site-metrics/advanced-metrics.py
    def _count_glossary_terms(self) -> int:
        """Counts glossary terms."""
        try:
            glossary_path = self.docs_path / 'glossary.md'
            if not glossary_path.exists():
                return 0
            with open(glossary_path, 'r', encoding='utf-8') as f:
                content = f.read()
                terms = re.findall(r'^####\s+\S.*$', content, re.MULTILINE)
                return len(terms)
        except Exception as e:
            print(f"Warning: Error counting glossary terms: {str(e)}")
            return 0
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
_get_basic_metrics method · python · L91-L100 (10 LOC)
src/site-metrics/advanced-metrics.py
    def _get_basic_metrics(self) -> Dict[str, int]:
        """Gets basic quantitative metrics."""
        return {
            'markdown_files': len(list(self.docs_path.glob('**/*.md'))),
            'images': len(list(self.docs_path.glob('**/*.png'))) + 
                     len(list(self.docs_path.glob('**/*.jpg'))),
            'word_count': self._count_words(),
            'microsims': self._count_microsims(),
            'glossary_terms': self._count_glossary_terms()
        }
_analyze_content_structure method · python · L102-L115 (14 LOC)
src/site-metrics/advanced-metrics.py
    def _analyze_content_structure(self) -> Dict[str, Any]:
        """Analyzes the content structure and organization."""
        try:
            with open(self.mkdocs_path, 'r') as f:
                mkdocs_config = yaml.safe_load(f)
                nav_structure = mkdocs_config.get('nav', [])
        except Exception:
            nav_structure = []
            
        return {
            'navigation_depth': self._calculate_nav_depth(nav_structure),
            'admonitions': self._count_admonitions(),
            'code_examples': self._count_code_blocks()
        }
_calculate_nav_depth method · python · L117-L123 (7 LOC)
src/site-metrics/advanced-metrics.py
    def _calculate_nav_depth(self, nav: List) -> int:
        """Calculates maximum navigation depth."""
        def get_depth(item) -> int:
            if isinstance(item, dict):
                return 1 + max((get_depth(v) for v in item.values()), default=0)
            return 0
        return max((get_depth(item) for item in nav), default=0)
_count_admonitions method · python · L125-L135 (11 LOC)
src/site-metrics/advanced-metrics.py
    def _count_admonitions(self) -> int:
        """Counts admonition blocks in content."""
        count = 0
        for md_file in self.docs_path.glob('**/*.md'):
            try:
                with open(md_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                    count += len(re.findall(r'!!!.*?\n', content))
            except Exception:
                continue
        return count
_count_code_blocks method · python · L137-L147 (11 LOC)
src/site-metrics/advanced-metrics.py
    def _count_code_blocks(self) -> int:
        """Counts code blocks in markdown files."""
        count = 0
        for md_file in self.docs_path.glob('**/*.md'):
            try:
                with open(md_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                    count += len(re.findall(r'```[a-zA-Z0-9]*\n[\s\S]*?\n```', content))
            except Exception:
                continue
        return count
_analyze_engagement_features method · python · L149-L154 (6 LOC)
src/site-metrics/advanced-metrics.py
    def _analyze_engagement_features(self) -> Dict[str, Any]:
        """Analyzes engagement and interactive features."""
        return {
            'microsim_complexity': self._analyze_microsim_complexity(),
            'has_analytics': self._check_analytics_config()
        }
_analyze_microsim_complexity method · python · L156-L187 (32 LOC)
src/site-metrics/advanced-metrics.py
    def _analyze_microsim_complexity(self) -> Dict[str, int]:
        """Analyzes complexity of MicroSims based on code size."""
        sims_dir = self.docs_path / 'sims'
        if not sims_dir.exists():
            return {'simple': 0, 'medium': 0, 'complex': 0}
            
        complexities = {'simple': 0, 'medium': 0, 'complex': 0}
        
        for sim_dir in sims_dir.glob('*'):
            if not sim_dir.is_dir():
                continue
                
            # Count files and code lines in sim directory
            file_count = len(list(sim_dir.glob('**/*.*')))
            code_lines = 0
            
            for file in sim_dir.glob('**/*.js'):
                try:
                    with open(file, 'r') as f:
                        code_lines += len(f.readlines())
                except Exception:
                    continue
                    
            # Classify complexity
            if code_lines < 100:
                complexities['simple'] += 1
_check_analytics_config method · python · L189-L198 (10 LOC)
src/site-metrics/advanced-metrics.py
    def _check_analytics_config(self) -> bool:
        """Checks if Google Analytics is configured."""
        try:
            with open(self.mkdocs_path, 'r') as f:
                config = yaml.safe_load(f)
                extra = config.get('extra', {})
                analytics = extra.get('analytics', {})
                return bool(analytics.get('provider') and analytics.get('property'))
        except Exception:
            return False
Repobility · open methodology · https://repobility.com/research/
_analyze_technical_quality method · python · L200-L205 (6 LOC)
src/site-metrics/advanced-metrics.py
    def _analyze_technical_quality(self) -> Dict[str, Any]:
        """Analyzes technical implementation quality."""
        return {
            'build_config_complete': self._verify_build_config(),
            'responsive_design': self._check_responsive_design()
        }
_verify_build_config method · python · L207-L223 (17 LOC)
src/site-metrics/advanced-metrics.py
    def _verify_build_config(self) -> Dict[str, bool]:
        """Verifies completeness of mkdocs.yml configuration."""
        required_fields = {
            'site_name': False,
            'theme': False,
            'nav': False
        }
        
        try:
            with open(self.mkdocs_path, 'r') as f:
                config = yaml.safe_load(f)
                for field in required_fields:
                    required_fields[field] = field in config
        except Exception:
            pass
            
        return required_fields
_check_responsive_design method · python · L225-L239 (15 LOC)
src/site-metrics/advanced-metrics.py
    def _check_responsive_design(self) -> Dict[str, bool]:
        """Checks for responsive design features."""
        responsive_features = {
            'mobile_navigation': False
        }
        
        try:
            with open(self.mkdocs_path, 'r') as f:
                config = yaml.safe_load(f)
                theme_features = config.get('theme', {}).get('features', [])
                responsive_features['mobile_navigation'] = 'navigation.tabs.mobile' in theme_features
        except Exception:
            pass
            
        return responsive_features
generate_report method · python · L241-L278 (38 LOC)
src/site-metrics/advanced-metrics.py
    def generate_report(self) -> str:
        """Generates a human-readable quality report."""
        metrics = self.analyze()
        
        report = []
        report.append("# Intelligent Textbook Quality Report\n")
        
        # Basic Metrics
        report.append("## Basic Metrics")
        for key, value in metrics['basic_metrics'].items():
            report.append(f"- {key.replace('_', ' ').title()}: {value}")
        
        # Content Structure
        report.append("\n## Content Structure")
        for key, value in metrics['content_structure'].items():
            report.append(f"- {key.replace('_', ' ').title()}: {value}")
        
        # Engagement Features
        report.append("\n## Engagement Features")
        for key, value in metrics['engagement_features'].items():
            if isinstance(value, dict):
                report.append(f"- {key.replace('_', ' ').title()}:")
                for subkey, subvalue in value.items():
                    report.ap
main function · python · L280-L294 (15 LOC)
src/site-metrics/advanced-metrics.py
def main():
    import argparse
    parser = argparse.ArgumentParser(description='Analyze intelligent textbook quality metrics')
    parser.add_argument('repo_path', help='Path to the textbook repository')
    parser.add_argument('--format', choices=['json', 'text'], default='text',
                      help='Output format (default: text)')
    
    args = parser.parse_args()
    
    analyzer = TextbookAnalyzer(args.repo_path)
    
    if args.format == 'json':
        print(json.dumps(analyzer.analyze(), indent=2))
    else:
        print(analyzer.generate_report())
extract_details_content function · python · L13-L67 (55 LOC)
src/site-metrics/analyze-details-content.py
def extract_details_content(docs_dir: Path) -> List[Dict]:
    """Extract all <details> tag content from chapter markdown files."""
    chapters_dir = docs_dir / 'chapters'
    if not chapters_dir.exists():
        return []

    details_list = []

    for chapter_dir in sorted(chapters_dir.iterdir()):
        if not chapter_dir.is_dir():
            continue

        index_file = chapter_dir / 'index.md'
        if not index_file.exists():
            continue

        with open(index_file, 'r', encoding='utf-8') as f:
            content = f.read()

            # Extract chapter title
            title_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
            chapter_title = title_match.group(1) if title_match else chapter_dir.name

            # Find all <details> blocks
            details_pattern = r'<details>(.*?)</details>'
            matches = re.finditer(details_pattern, content, re.DOTALL | re.IGNORECASE)

            for match in matches:
                details_c
categorize_visualization_types function · python · L70-L78 (9 LOC)
src/site-metrics/analyze-details-content.py
def categorize_visualization_types(details_list: List[Dict]) -> Dict[str, List[Dict]]:
    """Group visualizations by type."""
    categories = defaultdict(list)

    for item in details_list:
        viz_type = item['type'].lower()
        categories[viz_type].append(item)

    return dict(categories)
assess_skill_priority function · python · L81-L131 (51 LOC)
src/site-metrics/analyze-details-content.py
def assess_skill_priority(categories: Dict[str, List[Dict]], existing_sims: List[str]) -> List[Dict]:
    """
    Assess priority for each visualization type based on:
    - Impact: How many instances exist (more = higher impact)
    - Effort: Similarity to existing MicroSims (less similar = higher effort)
    """

    # Define similarity to existing MicroSims (0-10 scale, 10 = very similar)
    similarity_scores = {
        'microsim': 10,  # Already have these
        'graph-model': 9,  # Very similar to graph viewer
        'infographic': 8,  # Similar to existing infographic template
        'diagram': 6,  # Medium similarity, structured layouts
        'network-diagram': 6,  # Similar to graph viewer but different focus
        'chart': 5,  # Different from current sims
        'workflow': 4,  # Sequential/timeline visualization
        'interactive': 7,  # General category, similar to existing
        'timeline': 3,  # Time-based, less similar
        'unknown': 5,  # Default mid
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
generate_markdown_report function · python · L134-L261 (128 LOC)
src/site-metrics/analyze-details-content.py
def generate_markdown_report(details_list: List[Dict],
                            categories: Dict[str, List[Dict]],
                            priorities: List[Dict],
                            output_file: Path) -> None:
    """Generate a markdown report of the details analysis."""

    with open(output_file, 'w', encoding='utf-8') as f:
        f.write("# Details Tag Content Analysis\n\n")
        f.write("This report analyzes all `<details>` tags in the textbook chapters to categorize visualization types and prioritize skill development.\n\n")

        f.write("## Summary Statistics\n\n")
        f.write(f"- **Total `<details>` tags:** {len(details_list)}\n")
        f.write(f"- **Unique visualization types:** {len(categories)}\n\n")

        f.write("## Visualization Type Distribution\n\n")
        f.write("| Type | Count | Percentage |\n")
        f.write("|------|-------|------------|\n")

        total = len(details_list)
        for viz_type, items in sorted(categories.item
main function · python · L264-L302 (39 LOC)
src/site-metrics/analyze-details-content.py
def main():
    """Main function to analyze details content."""
    repo_root = Path(__file__).parent.parent.parent
    docs_dir = repo_root / 'docs'

    if not docs_dir.exists():
        print(f"Error: docs directory not found at {docs_dir}")
        return 1

    print("Analyzing <details> tag content...")

    # Extract all details content
    details_list = extract_details_content(docs_dir)

    if not details_list:
        print("No <details> tags found in chapters")
        return 1

    # Categorize by type
    categories = categorize_visualization_types(details_list)

    # List existing MicroSims
    sims_dir = docs_dir / 'sims'
    existing_sims = [d.name for d in sims_dir.iterdir() if d.is_dir()] if sims_dir.exists() else []

    # Assess priorities
    priorities = assess_skill_priority(categories, existing_sims)

    # Generate report
    output_file = docs_dir / 'details-analysis.md'
    generate_markdown_report(details_list, categories, priorities, output_file)

    pri
count_chapters function · python · L14-L22 (9 LOC)
src/site-metrics/get-ibook-metrics.py
def count_chapters(docs_dir: Path) -> int:
    """Count the number of chapters in the docs/chapters directory."""
    chapters_dir = docs_dir / 'chapters'
    if not chapters_dir.exists():
        return 0
    # Count directories that contain index.md, excluding the chapters index itself
    chapters = [d for d in chapters_dir.iterdir()
                if d.is_dir() and (d / 'index.md').exists()]
    return len(chapters)
analyze_chapters function · python · L25-L73 (49 LOC)
src/site-metrics/get-ibook-metrics.py
def analyze_chapters(docs_dir: Path) -> Dict[str, any]:
    """Analyze chapter structure and content."""
    chapters_dir = docs_dir / 'chapters'
    if not chapters_dir.exists():
        return {'count': 0, 'sections': [], 'details': []}

    chapters = sorted([d for d in chapters_dir.iterdir()
                      if d.is_dir() and (d / 'index.md').exists()])

    total_sections = 0
    total_details = 0
    chapter_data = []

    for chapter_dir in chapters:
        index_file = chapter_dir / 'index.md'
        with open(index_file, 'r', encoding='utf-8') as f:
            content = f.read()

            # Count level 2 headers (## ) as sections
            sections = len(re.findall(r'^##\s+[^#]', content, re.MULTILINE))

            # Count <details> tags
            details = len(re.findall(r'<details>', content, re.IGNORECASE))

            # Get chapter title (first # header)
            title_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
            title = title_mat
count_learning_graph_concepts function · python · L76-L85 (10 LOC)
src/site-metrics/get-ibook-metrics.py
def count_learning_graph_concepts(docs_dir: Path) -> int:
    """Count concepts in the learning graph CSV file."""
    lg_file = docs_dir / 'learning-graph' / 'learning-graph.csv'
    if not lg_file.exists():
        return 0

    with open(lg_file, 'r', encoding='utf-8') as f:
        lines = f.readlines()
        # Subtract 1 for header row
        return len(lines) - 1 if len(lines) > 1 else 0
count_glossary_terms function · python · L88-L98 (11 LOC)
src/site-metrics/get-ibook-metrics.py
def count_glossary_terms(docs_dir: Path) -> int:
    """Count number of level 4 headers (####) in the glossary.md file."""
    glossary_path = docs_dir / 'glossary.md'
    if not glossary_path.exists():
        return 0

    with open(glossary_path, 'r', encoding='utf-8') as f:
        content = f.read()
        # Count level 4 headers (####)
        terms = re.findall(r'^####\s+', content, re.MULTILINE)
        return len(terms)
count_microsims function · python · L101-L106 (6 LOC)
src/site-metrics/get-ibook-metrics.py
def count_microsims(docs_dir: Path) -> int:
    """Count number of MicroSims in the /docs/sims directory."""
    sims_dir = docs_dir / 'sims'
    if not sims_dir.exists():
        return 0
    return len([d for d in sims_dir.iterdir() if d.is_dir()])
count_markdown_files function · python · L109-L111 (3 LOC)
src/site-metrics/get-ibook-metrics.py
def count_markdown_files(docs_dir: Path) -> int:
    """Count total number of markdown files in the docs directory."""
    return len(list(docs_dir.glob('**/*.md')))
All rows scored by the Repobility analyzer (https://repobility.com)
count_images function · python · L114-L130 (17 LOC)
src/site-metrics/get-ibook-metrics.py
def count_images(docs_dir: Path) -> Tuple[int, Dict[str, int]]:
    """Count total number of image files in the docs directory."""
    png_count = len(list(docs_dir.glob('**/*.png')))
    jpg_count = len(list(docs_dir.glob('**/*.jpg')))
    jpeg_count = len(list(docs_dir.glob('**/*.jpeg')))
    svg_count = len(list(docs_dir.glob('**/*.svg')))
    gif_count = len(list(docs_dir.glob('**/*.gif')))

    breakdown = {
        'PNG': png_count,
        'JPG': jpg_count,
        'JPEG': jpeg_count,
        'SVG': svg_count,
        'GIF': gif_count
    }

    return sum(breakdown.values()), breakdown
count_words_in_markdown function · python · L133-L150 (18 LOC)
src/site-metrics/get-ibook-metrics.py
def count_words_in_markdown(docs_dir: Path) -> int:
    """Count total number of words in all markdown files."""
    total_words = 0
    for md_file in docs_dir.glob('**/*.md'):
        with open(md_file, 'r', encoding='utf-8') as f:
            content = f.read()
            # Remove code blocks
            content = re.sub(r'```.*?```', '', content, flags=re.DOTALL)
            # Remove inline code
            content = re.sub(r'`.*?`', '', content)
            # Remove URLs
            content = re.sub(r'http[s]?://\S+', '', content)
            # Remove HTML tags
            content = re.sub(r'<.*?>', '', content)
            # Split and count remaining words
            words = content.split()
            total_words += len(words)
    return total_words
count_prompts function · python · L153-L158 (6 LOC)
src/site-metrics/get-ibook-metrics.py
def count_prompts(docs_dir: Path) -> int:
    """Count number of AI prompts in the prompts directory."""
    prompts_dir = docs_dir / 'prompts'
    if not prompts_dir.exists():
        return 0
    return len(list(prompts_dir.glob('*.md'))) - 1  # Exclude index.md
page 1 / 2next ›